@avandar/acclimate
v0.3.2
Published
A lightweight TypeScript framework for building CLI tools.
Readme
Acclimate
A lightweight TypeScript framework for building type-safe command line interfaces.
This library is not complete and is still missing a lot of functionality. We do not recommend this library be used in production.
See the To Do section at the end for what is still missing.
Installation
npm install acclimateQuick start
import { Acclimate } from "acclimate";
const cli = Acclimate.createCLI("demo-cli")
.description("A tiny demo CLI")
.action(() => {
console.log("Hello, world!");
});
Acclimate.run(cli);API
Public exports from acclimate:
Acclimate
Acclimate
Acclimate.createCLI(name)
Create a new CLI instance.
Acclimate.createCLI(name: string): IAcclimateCLIAcclimate.run(cli)
Run a CLI instance using process.argv.slice(2).
Acclimate.run(cli: IAcclimateCLI): voidIAcclimateCLI (CLI builder)
createCLI() returns an immutable builder. Each method returns a new CLI
instance with updated configuration.
cli.description(description)
cli.description(description: string): IAcclimateCLIcli.action(action)
Set the function executed when the CLI matches (after parsing).
cli.action(
action: (args: FullCLIArgValues<...>) => void,
): IAcclimateCLIcli.addPositionalArg(param)
Add a positional argument. Positional args are parsed in order.
cli.addPositionalArg(param: CLIPositionalParam): IAcclimateCLIcli.addOption(param)
Add an option local to this CLI level.
cli.addOption(param: CLIOptionParam): IAcclimateCLIcli.addGlobalOption(param)
Add an option that is available to this CLI and all sub-commands.
cli.addGlobalOption(param: CLIOptionParam): IAcclimateCLIcli.addCommand(commandName, commandCLI)
Add a sub-command (a nested CLI). If the first positional token matches a command name, parsing continues using that command's CLI.
cli.addCommand(
commandName: string,
commandCLI: IAcclimateCLI,
): IAcclimateCLIcli.getCommandCLI(commandName)
Get a command CLI by name (throws if missing).
cli.getCommandCLI(commandName: string): IAcclimateCLIParam config types
Acclimate uses config objects to describe positional args and options.
Positional args:
CLIPositionalParam- name:
string(prefercamelCaseto match runtime parsing) - type:
"string" | "number" | "boolean" - required:
boolean - description?:
string - defaultValue?: depends on
type - choices?: allowed values list
- parser?:
(value: string) => value - validator?:
(value) => true | string
- name:
Options:
CLIOptionParam- name:
--${string}(example:--dry-run) - aliases?:
readonly ("--x" | "-x")[] - required:
boolean - Same
type,defaultValue,choices,parser,validatorfields as a positional arg
- name:
Parsing behavior (current)
- Positional args: validated and parsed in order; extra positional args throw an error.
- Options: parsing starts at the first token that begins with
-. Each option consumes tokens until the next option; its raw value is the consumed tokens joined with spaces. - Option keys in
action(args): option names are camel-cased, so--dry-runbecomesdryRun. - Boolean flags:
--flagwith no value parses astrue(only the literal stringfalseparses asfalse). - Errors: missing required args/options (and invalid values) throw
CLIError.
Prerequisites
- Node.js 18+
- npm (bundled with Node)
Project Layout
src/— framework source code.examples/— small usage samples;basic.tsis runnable vianpm run demo.tests/— Vitest suite.dist/— build output generated bytsup.
Setup
Install dependencies:
npm installCommon Scripts
npm run dev— build in watch mode with tsup.npm run build— produce CJS/ESM bundles and type declarations indist/.npm run demo— execute theexamples/basic.tssample with tsx.npm test— run the Vitest suite once.npm run test:watch— run tests in watch mode.npm run lint/npm run lint:fix— check or auto-fix with ESLint.npm run format/npm run format:fix— check or write Prettier formatting.npm run type- check typescript types
Running & Testing
- Start a build (optional during development):
npm run dev- Run the sample CLI:
npm run demo- Execute tests:
npm testThe prepare script runs npm run build, so the package will compile
automatically when installed from a git dependency.
Examples
Hello world
Matches examples/basic.ts.
import { Acclimate } from "acclimate";
const cli = Acclimate.createCLI("demo-cli").action(() => {
console.log("Hello, world!");
});
Acclimate.run(cli);Positional args + options
import { Acclimate } from "acclimate";
const cli = Acclimate.createCLI("greet")
.addPositionalArg({
name: "name",
type: "string",
required: true,
description: "Who to greet",
})
.addOption({
name: "--shout",
type: "boolean",
required: false,
aliases: ["-s"] as const,
description: "Uppercase the output",
defaultValue: false,
})
.action(({ name, shout }) => {
const message = `Hello, ${name}!`;
console.log(shout ? message.toUpperCase() : message);
});
Acclimate.run(cli);Sub-commands + global options
import { Acclimate } from "acclimate";
const initCmd = Acclimate.createCLI("init")
.description("Initialize a project")
.action(({ verbose }) => {
console.log(`init (verbose=${verbose})`);
});
const root = Acclimate.createCLI("acme")
.addGlobalOption({
name: "--verbose",
type: "boolean",
required: false,
aliases: ["-v"] as const,
defaultValue: false,
})
.addCommand("init", initCmd);
Acclimate.run(root);To do
- [ ] Add semantic arguments: e.g. email (which has a default validator)
- [ ] Add default
helpcommand and--helpoption. This should show the CLI description and all param documentation. - [ ] Make the
descriptionactually get printed. - [ ] Show all CLI param descriptions if command is run with no arguments or if there is a param-related error.
- [ ] Add helper functions to log to stdout in different colors
- [ ] Add logic for
askIfEmptyto enter an interactive mode to receive inputs for different params. - [ ] Add an option to only
askIfEmptyAndRequired.
