npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ismail-elkorchi/cli-core

v0.4.0

Published

Parser-independent command routing, help, completion, and dispatch primitives.

Readme

@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-core

Quick 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",
    description: "Deploy the billing service in Europe.",
  }],
  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. Set invokable: false on a grouping command that requires a child command. Each command 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.

Connect an option grammar

createCliInvocationParser() accepts a CliOptionBinder that creates one isolated grammar session per invocation:

  • create(argv) receives the immutable original argument snapshot.
  • The session's next(command) classifies one contiguous span under the current command's option scope. It advances nextIndex and reports indexed options, ordinary arguments, integration controls, unknown flags, diagnostics, and explicitly unclassified syntax.
  • The session's bind(command) decodes its retained occurrences once, after the command is selected. It must not reparse raw argv to rediscover ownership. Both callbacks receive the immutable CliCommand directly; its options already contains the effective inherited option declarations.

Core validates span ownership and current-scope option membership, adopts its own immutable classification, and resolves child command tokens as traversal advances. route() returns this classification without decoding; bind(route) accepts only a route produced by the same parser. parse() performs both steps. Repeated binding of a route never repeats decoder effects.

Recognized options with malformed values retain command context and diagnostics; they cannot produce a ready invocation. Unclassifiable syntax or an unknown flag before a child command stops routing. The remaining suffix stays unclassified, so an option value cannot be reinterpreted as a help or version control. CLI_ROUTING_UNCERTAIN reports that structural failure even under the collect policy. Ordinary unknown-flag diagnostics are added by core when the policy is error, consistently on successful and failed routes.

Place command-local flags after the command that declares them. Ancestor flags may appear around descendant command tokens because descendants inherit those options. Integration controls are classified separately from domain options; they do not need fake entries in the command definition.

Use createCliInvocation() when an adapter already has decoded option and positional values. The returned invocation records a structured source and can carry an application-defined sourceId. This constructor validates names, presence and positional shape; integrations must decode and validate option values before calling it. Arbitrary decoded application objects remain owned by the decoder. Custom nested diagnostic details likewise remain application-owned. Framework-owned arrays, records, and diagnostic containers are snapshots; accessors and custom array behavior are rejected rather than executed.

Results and diagnostics

A ready invocation exposes:

  • commandKey as the command-specific discriminant;
  • the compiled command;
  • decoded option and positional values;
  • explicit option-presence information;
  • used aliases and deprecation warnings;
  • passthrough arguments and collected unknown flags;
  • an argv or structured source.

Literal definitions produce a union for every invokable command, so handlers receive the exact command key they implement. CliHandlers requires every invokable key. Invalid results contain structured diagnostics and unknown flags without partial values.

Core diagnostics are discriminated by source and code. Option integrations can create immutable option diagnostics with createCliOptionDiagnostic(). Each stage contributes diagnostics once: next() reports lexical diagnostics, while bind() reports decoding diagnostics without repeating lexical ones. An invalid binding may contribute no new diagnostics only when an error has already been retained from scanning. Core preserves distinct diagnostic entries and does not infer equality from messages or application-defined details. Framework-owned diagnostic records are immutable; nested application payloads remain application-owned.

Help, completion, and dispatch

createCliHelp() returns renderer-neutral usage, examples, command, alias, positional, and option data. completeCli() returns command, alias, flag, and finite option-value candidates. Both return undefined for an unknown canonical command path.

dispatchCli() sends a ready invocation to its canonical handler and returns the handler result. Handler errors propagate to the integration that owns application error policy.

Runtime support

  • ESM
  • Node.js 24 or later
  • Deno 2.6 or later
  • Bun 1.3 or later
  • Zero runtime dependencies

License

MIT

Migrating to 0.4

Replace independent scanner/binder calls with one createCliInvocationParser() whose binder creates a session for each argv snapshot. Use next(command) to classify one owned span and bind(command) to decode retained occurrences once. Callers that used a scope wrapper should read the supplied command directly, including command.options; CliOptionScope is removed without an alias.

Keep routes with their creating parser. Use route() before deciding whether to show help or version information, and bind(route) only when decoding is needed. Separate lexical and decoding diagnostic contributions as described above; integrations no longer repair core unknown-flag policy results.

Compiled program types now retain their definition identity. Different literal command trees are not interchangeable merely because their root names match. Union definitions keep correlated command keys. Optional, non-tuple, or union-valued child collections and union-valued tuple slots retain known leading names and widen descendants rather than claiming a finite exhaustive command set. Fixed finite literal tuples stay precise.