Files
2026-05-27 23:29:53 +01:00

203 lines
14 KiB
Markdown

# @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.
---
<!-- begin:auto-docs -->
## Documentation tree
- [app](./src/app/app.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [app](./src/app/app.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [app](./src/app/app.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_- [app](./src/app/app.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_- [app](./src/app/app.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_- [app](./src/app/app.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_ - [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_ - [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_ - [format](./src/output/format/format.md) — <!-- begin:auto-docs --> _(no submodules)_- [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_ - [table](./src/output/table/table.md) — <!-- begin:auto-docs --> _(no submodules)_- [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_ - [parser](./src/parser/parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_ - [progress](./src/progress/progress.md) — <!-- begin:auto-docs --> _(no submodules)_- [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_ - [prompt](./src/prompt/prompt.md) — <!-- begin:auto-docs --> _(no submodules)_- [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_ - [spinner](./src/spinner/spinner.md) — <!-- begin:auto-docs --> _(no submodules)_- [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_ - [style](./src/style/style.md) — <!-- begin:auto-docs --> _(no submodules)_
## Modules
| Module | Path | Description |
|---|---|---|
| [App / cli()](src/app/app.md) | `app/app` | Application entry point and root command |
| [Command](src/command/command.md) | `command/command` | Subcommand builder with fluent API |
| [Parser](src/parser/parser.md) | `parser/parser` | Argv parser with options, positionals, and short flags |
| [Errors](src/errors/errors.md) | `errors/errors` | Stable error codes and `CliError` class |
| [Style](src/style/style.md) | `style/style` | ANSI color and formatting with chainable proxy styler |
| [Format](src/output/format/format.md) | `output/format/format` | Text formatters: key-value, list, tree, columns, heading |
| [Table](src/output/table/table.md) | `output/table/table` | ASCII/Unicode bordered table renderer |
| [Spinner](src/spinner/spinner.md) | `spinner/spinner` | TTY-aware animated spinner with AbortSignal |
| [Progress](src/progress/progress.md) | `progress/progress` | ASCII progress bar with TTY in-place update |
| [Prompt](src/prompt/prompt.md) | `prompt/prompt` | Interactive prompts: text, password, confirm, select, multiselect |
---
## Quick Start
```ts
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
```ts
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
```ts
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
```ts
import { createSpinner } from "@suaveplan/cli";
const sp = createSpinner({ text: "Building…" });
sp.start();
// later…
sp.succeed("Build complete");
// or
sp.fail("Build failed");
```
### Progress Bar
```ts
import { createProgress } from "@suaveplan/cli";
const bar = createProgress({ total: 100, label: "Compiling" });
bar.update(42);
bar.complete("Done");
```
### Interactive Prompts
```ts
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
```ts
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.