@ismail-elkorchi/cli-core
Build reusable, typed command systems for argv parsers, graphical interfaces, HTTP endpoints, tests, and other invocation sources.
cli-core compiles immutable command trees, routes scanner-classified command tokens, validates decoded invocations, binds positional values, produces help and completion data, and dispatches command-specific handlers. Its explicit scanner and binder boundary lets each integration choose its own flag grammar.
Clivoke combines cli-core with argv-flags and adds process and shell integration. Its README lists the available installation methods and end-to-end adapters.
Install
npm install @ismail-elkorchi/cli-core
deno add jsr:@ismail-elkorchi/cli-coreQuick start
Define the command tree once, then use it from structured adapters, help, completion, and dispatch:
import {
createCliHelp,
createCliInvocation,
defineCli,
dispatchCli,
} from "@ismail-elkorchi/cli-core";
const program = defineCli({
name: "ship",
invokable: false,
examples: [{
usage: "ship deploy billing --region eu",}], options: [ { name: "verbose", kind: "boolean", flags: ["-v", "--verbose"] }, ], commands: [{ name: "deploy", aliases: ["d"], description: "Deploy one service.", options: [{ name: "region", kind: "value", flags: ["--region"], valueMode: "required", required: true, valueCandidates: ["eu", "us"], }], positionals: [{ name: "service" }], }], });
const help = createCliHelp(program, ["deploy"]); if (help === undefined) throw new Error("deploy help is unavailable");
const invocation = createCliInvocation(program, { sourceId: "deployment-api", commandPath: ["deploy"], optionValues: { region: "eu" }, specifiedOptions: { verbose: false, region: true }, positionalValues: { service: "billing" }, });
if (invocation.status === "invalid") { throw new Error(invocation.diagnostics.map(({ message }) = message).join("\n")); }
await dispatchCli(invocation, { "ship deploy": ({ invocation: deploy }) = ({ service: deploy.positionalValues.service, region: deploy.optionValues.region, }), }, undefined);
Definitions are closed in TypeScript and at runtime. `defineCli()` returns an
immutable `CliProgram` or throws one `CliDefinitionError` containing all
definition issues found in the tree.
## Command model
Options declared on the root are global. Options declared on a command are
inherited by its descendants, preserving where each option originated for help
and completion.
Every command has a stable canonical key such as `ship deploy`. Setcommand chooses child-command routing or positional binding, keeping command tokens unambiguous.
The root and child commands share the same positional and passthrough model. This supports shapes such as formatter <file, archive <inputs..., and runner -- node app.js directly. Set acceptsPassthroughArguments: true when post--- tokens belong to the selected command.
Option definitions contain parser-neutral facts used by routing and presentation: flag spellings, value mode, requiredness, repetition, multiplicity, defaults, false flags, finite value candidates, and descriptive labels. Integrations remain responsible for decoding option values.