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

icore

v2.2.4

Published

Typed CLI command mechanics for Node.js that keep argument parsing separate from application logic

Readme

icore

English | Русский | 简体中文

npm version node types license

Small dependency-free command line interface and terminal presentation mechanics for Node.js® applications.

icore owns the path from process.argv to terminal output:

  • declarative, type-inferred option schemas;
  • multi-segment command resolution with canonical command aliases;
  • separate prepare and execute phases;
  • JSON, CSV, and text-table presentation;
  • backpressure-aware stdout/stderr writers and reusable terminal error policy.

Application-specific API calls, configuration, domain behavior, and help text remain in the consuming application.

Requirements And Installation

icore requires Node.js >=16.9.0 and includes TypeScript declarations.

npm install icore

Import public APIs from icore. Deep imports into dist or src are not part of the package contract.

Contents

Quick Start

Create commands, put them in a registry, and give that registry to createTerminalApp():

import {
  createCommand,
  createTerminalApp
} from 'icore';

const command = createCommand();

const commands = command.registry([
  command.define({
    path: ['hello'],
    options: {
      name: {
        type: 'string',
        default: 'world'
      },
      uppercase: {
        type: 'boolean'
      }
    },
    handle({ options }) {
      const greeting = `Hello, ${options.name}!`;

      return `${options.uppercase ? greeting.toUpperCase() : greeting}\n`;
    }
  })
] as const);

const app = createTerminalApp({ commands });

async function main(args: readonly string[]): Promise<void> {
  process.exitCode = await app.run(args, undefined, {
    strict: true
  });
}

void main(process.argv.slice(2));

After compiling the application:

$ node dist/cli.js hello
Hello, world!

$ node dist/cli.js hello --name Alice --uppercase
HELLO, ALICE!

The schema determines the handler's option types. Required options and options with defaults are always present; optional options are returned as T | undefined.

Applications that share command context, result, and metadata contracts can bind those types once with createCommand.withTypes<...>(). Per-command schema, path, aliases, prepared payload, and concrete result types remain inferred. See Bind application command types once.

app.run(...) is the compact path. Applications that handle global shortcuts, select runtime resources after preparation, or own cleanup should continue with the production terminal lifecycle.

strict: true rejects options placed before the command path. String output is written exactly as returned, so include \n when line output is intended. Default resolution continues to accept option-first input. Parsing bootstrap options does not reorder argv, so enable strict mode only when command-first syntax is part of the application's public CLI contract.

Choose An API Level

Start with the highest-level API that fits the application:

| Need | Start with | Detailed guide | | --- | --- | --- | | Simple terminal application | app.run(...) | Quick Start | | Production shortcuts and resource lifecycle | app.prepare(...) and app.commands.run(...) | Production Terminal Application | | Commands and option schemas | createCommand(), createCommand.withTypes(), or createCommands() | Option Schemas | | Custom prepare/execute lifecycle | commands.prepare() and commands.run() | Custom Command Flow | | One flat projection for every format | createPresentation() | Presentation And Output | | Different JSON, table, or CSV projections | renderJson(), renderTextTable(), or renderCsv() | Presentation And Output | | Explicit stdout/stderr writing | createOutput() | Output Writers | | Parser and resolver primitives | parseArgv(), resolveCommand(), and related exports | Primitive Mechanics |

createTerminalOutput() and createTerminalProgress() remain available as deprecated 2.x compatibility exports. Do not use them in new Consumers; keep interactive output and progress rendering in the consuming application. createOutput() preserves sink backpressure but does not serialize application-owned progress with regular output. The application-owned output guide describes the recommended boundary. The retained migration guide documents the legacy contract.

The terminal application exposes the main lifecycle operations:

  • app.run(args, context, options?) prepares, executes, renders, and writes;
  • app.prepare(args, options?) validates input without runtime context;
  • app.runPrepared(prepared, context) executes an already prepared command;
  • app.writePreparedOutput(prepared, output) writes caller-obtained output;
  • app.reportError(error, context?) applies the configured terminal error policy and returns a process-style exit code.

An empty command registry is supported for a bootstrap-only TerminalApp that uses caller-owned global shortcuts and needs presentation, output, or reportError(...) before the full registry is loaded. It has no resolvable commands; construct the command application separately and reuse the same output and error policy instead of replacing the registry in place.

Command handlers receive parsed options, option-presence metadata in provided, remaining positionals, caller-owned context, and an optional prepared payload.

Supported terminal results are strings, async string streams, presentation results, and undefined.

Use isTerminalCommandOutput(...) to narrow an unknown caller-owned result before passing it to app.writePreparedOutput(...).

Text-table rendering supports plain-text cells. Column widths use JavaScript string length, so ANSI sequences, emoji, combining characters, tabs, and full-width Unicode may be aligned incorrectly.

argv → resolve → validate/prepare → execute → render → stdout/stderr

Exact public exports live in src/index.ts and in the bundled TypeScript declarations. Consumers import them from the package root; internal module paths are not exported.

Supported Argument Syntax

The supported syntax is intentionally small and predictable:

| Form | Example | Notes | | --- | --- | --- | | Long option with separate value | --name Alice | String and number options consume a value | | Long option with attached value | --name=Alice | Equivalent to the separate form | | Boolean flag | --verbose | Produces true | | Boolean negation | --no-cache | Supported for known boolean options unless syntax: 'flag' | | Short alias | -v, -n Alice | Must be declared in the schema as one ASCII letter | | Option terminator | -- | Every following token becomes positional |

Option names are exact: icore does not convert camelCase to kebab-case. Explicit boolean values such as --verbose=true, attached short values such as -nAlice, and grouped aliases such as -abc are not supported.

See CLI Argument Syntax for parsing examples, edge cases, duplicate handling, and the option terminator contract.

Error Handling

CLI parsing, validation, resolution, and definition failures are reported as IcoreError. It extends Error with:

  • a stable machine-readable code;
  • a usage or definition category;
  • required, code-specific details.

Use isIcoreError(...) instead of manually casting details. Passing a code to the guard narrows the corresponding details shape:

import {
  createTerminalApp,
  isIcoreError,
  isUsageError
} from 'icore';

const help = 'Usage: cli hello [--name value]';

const app = createTerminalApp({
  commands,
  errorPolicy: {
    renderError(error) {
      const message = error instanceof Error
        ? error.message
        : String(error);

      if (isIcoreError(error, 'UNKNOWN_COMMAND')) {
        return `${message}\n\n${help}\n`;
      }

      return `${message}\n`;
    },
    resolveExitCode(error) {
      return isUsageError(error) ? 2 : 1;
    }
  }
});

Inside the UNKNOWN_COMMAND branch, fields such as error.details.command and error.details.positionals are strongly typed. IcoreErrorDetailsMap is the public source of truth for every code. Direct new IcoreError(...) calls must provide a third argument matching the selected code.

Application-owned semantic validation can throw CliUsageError without claiming one of the framework-owned IcoreError codes:

import { CliUsageError } from 'icore';

throw new CliUsageError(
  "Expected '--from' to be earlier than or equal to '--to'"
);

isUsageError(...) recognizes both CliUsageError and IcoreError instances whose category is usage. Rendering, help text, and the choice of exit code remain application policy.

Both guards recognize compatible branded errors created by another physical copy of icore in the same JavaScript realm. This does not turn errors serialized through JSON, IPC, or worker boundaries back into class instances.

Without a custom policy, terminal apps write Error.message + "\n" (or String(error) + "\n" for other thrown values) and return exit code 1. Application-specific help remains application policy.

Custom lifecycles can call app.reportError(...) to reuse the same rendering and exit-code policy. The complete prepare, execute, write, and external phase flow is shown in the production terminal lifecycle.

Guides

The guides index routes from regular terminal apps to lower-level mechanics. Useful starting points:

Release history is recorded in CHANGELOG.md. Directional decisions live in docs/roadmap.md.

Project Boundary

icore is a small terminal mechanics module. It owns generic behavior such as option validation, command resolution, typed handler input, presentation rendering, error contracts, and stdout/stderr delivery.

It does not own application DTO mapping, API calls, configuration loading, domain-specific validation, lifecycle resources, or help content. Keep those responsibilities in the consuming application. Interactive line rendering and progress reporting are also application-owned; the legacy compatibility exports are not a foundation for new code.

Licensed under the MIT License.