203 lines
14 KiB
Markdown
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.
|