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

@docspack/cmdspec

v0.2.0

Published

cmdspec: a machine-readable description of a command-line interface. Validate it, convert it to and from Usage and OpenCLI, and render a per-command digest for an agent.

Readme

@docspack/cmdspec

cmdspec: a machine-readable description of a command-line interface.

OpenAPI says what an HTTP API takes and returns. Nothing has said the same about a CLI with that standing: OpenCLI is a 0.1 draft with no value types, and Usage is rich but has no schema for its own documents. cmdspec is a format that contains both — anything either says converts into it without loss — and adds what neither says: whether a command is safe to run, what its exit statuses mean, what it prints, and which options apply to which operand.

cmdspec: "0.1"
info: { name: greet, version: 1.0.0 }
arguments:
  - { name: who, required: true }
options:
  - { name: --shout, aliases: [-s], summary: Print in capitals }
effects: []
examples:
  - run: greet world --shout

The CLI

npx @docspack/cmdspec validate cmdspec.yaml
npx @docspack/cmdspec convert mise.usage.kdl --to cmdspec --program-version 2026.10.0
npx @docspack/cmdspec convert cmdspec.yaml --to opencli -o opencli.json
npx @docspack/cmdspec digest cmdspec.yaml remote add
npx @docspack/cmdspec map cmdspec.yaml

validate reports every problem, not the first, and names the command and option each one is in. The CLI is itself defined by a cmdspec document — cmdspec.yaml: its parser, its --help and cmdspec --cmdspec all come from that one file.

The digest — what an agent reads

One command, everything needed to run it, in the shape of a help page:

docspack ask <question>... [options]
  Answer from the local index — the command to give an agent
  <question>...  words are joined with spaces
  -p, --package <s>  only packages whose name contains this text
  --limit <n>  maximum chunks to return (integer; at least 1; default 3)
  --max-tokens <n>  token ceiling for the result set (integer; at least 1; default 3000)
  --all  search the whole store, not just this project
  also accepts: --store <path>, --cwd <dir>, --json, -q, --quiet, -h, --help
  effects: read; idempotent
  stdout: text/markdown, the answer; application/json, the chunks and their scores with --json
  exit: 0 success; 1 the command failed; 2 the command was used wrongly; 3 nothing matched, and a docs package is installed but not indexed; 4 nothing matched, and everything installed is already indexed
  $ docspack ask "how do I verify a webhook signature"
  $ docspack ask "webhook signature" --package stripe --limit 5  # Look in one package only, and return more

It is not a notation: every annotation is a word, because the help page is the format a model has seen most, and the design doc measured that words cost 3.2% more than markers it would have to be taught. What it adds to a help page is what help pages leave out — types, defaults, effects, outputs and exit statuses. The largest digest across the four examples is 544 tokens.

Converting

| From → to | Loses | | --- | --- | | Usage → cmdspec | nothing: unmapped properties and nodes are kept in x-usage and written back | | OpenCLI → cmdspec | nothing: unmapped fields are kept as x-opencli-* | | cmdspec → OpenCLI | nothing: what OpenCLI cannot say travels in its metadata | | cmdspec → Usage | what Usage cannot say — listed, never dropped quietly |

Tested on real documents rather than on promises: mise's own Usage spec (290 commands) survives Usage → cmdspec → Usage → cmdspec unchanged and converts to a valid document, and dotnet's published OpenCLI document (813 KB) comes back from OpenCLI → cmdspec → OpenCLI as the same JSON.

As a library

import {
  commandDigest,
  fromOpenCli,
  mergeOverlay,
  parseArgsConfig,
  resolveDocument,
  toOpenCli,
} from "@docspack/cmdspec";
import { validate, validateOverlay } from "@docspack/cmdspec/validate";
import { readFile } from "@docspack/cmdspec/read";
import { fromUsage, toUsage } from "@docspack/cmdspec/usage";
import { fromCitty, fromCommander, fromYargs } from "@docspack/cmdspec/emit";

| Entry | Contains | Dependency | | --- | --- | --- | | @docspack/cmdspec | types, resolveDocument, the digest and map, OpenCLI conversion, parseArgsConfig, the rules, mergeOverlay | none | | @docspack/cmdspec/validate | validate — the schema, then the rules; validateOverlay | ajv | | @docspack/cmdspec/read | readFile, readText — JSON or YAML, validated | yaml | | @docspack/cmdspec/usage | fromUsage, toUsage | @bgotink/kdl | | @docspack/cmdspec/emit | fromCommander, fromCitty, fromYargs | none | | @docspack/cmdspec/builder | mountCommandBuilders — the command builder, in a browser | none |

parseArgsConfig(command) gives node:util's parseArgs its options from a command's document, so a Node CLI can parse its command line from the same file it is documented by.

A command line from a reader's choices

commandLine(builderTarget(command), values) writes the command line a set of values makes — attaching values the way the document's syntax says, quoting for a POSIX shell, putting -- before arguments that come after it — and lists the required inputs still missing. @docspack/sheaf-react's CliBuilder renders that as a form, and @docspack/cmdspec/builder runs it in the browser as the reader types.

From a commander, citty or yargs program

A program already built with one of these frameworks has its command line in memory. The emitters read it and write the document, so describing it starts from what it actually parses:

import { fromCommander } from "@docspack/cmdspec/emit";
import { program } from "./cli.js"; // the commander program, not yet parsed

const document = fromCommander(program);

fromCitty(command) resolves every lazy subcommand, so it is async. fromYargs(build) takes the function that builds the program, because yargs builds a command's options only while running it, on the program itself; it is called once per command, and the version comes from fromYargs(build, { version }). No framework is imported: each emitter reads the few fields it needs, and the entry has no dependencies.

What a framework does not know, the document does not say: effects, exit statuses, outputs and examples. Keep them in an overlay, so the next generation does not lose them; docspack build warns for each one still missing.

Keeping what a framework cannot know

An overlay is a file beside the program — schema/overlay-v0.1.json — merged into every document generated from it:

cmdspecOverlay: "0.1"
exits:
  "0": { meaning: success }
commands:
  serve:
    effects: [network]
    options:
      --port: { value: { minimum: 1024 } }
  db drop:
    effects: [destructive]
import { mergeOverlay } from "@docspack/cmdspec";

const result = mergeOverlay(fromCommander(program), overlay);
if (!result.merged) throw new Error(result.problems.map((p) => `${p.at}: ${p.message}`).join("\n"));

Commands are keyed by their path below the program, options by spelling and arguments by name. Every entry must name something the program has, so renaming db drop makes the overlay a problem rather than a patch that silently stops applying. The overlay fills what the document leaves out; where the program states a value, the overlay must agree or list the field in override. validateOverlay checks an overlay against its schema, and mergeOverlay reports every problem, not the first. Why it works this way is docs/design/cmdspec-adapters.md.

Finding a program's document

A package names its document in package.json — "cmdspec": "./dist/cmdspec.json", as this one does — and a program may print it when run with --cmdspec. Both are in SPEC §13.

Licence

The code, the schema and the examples are MIT. The specification text, SPEC.md, is CC BY 4.0. The fixtures are MIT-licensed documents from mise and OpenCLI; see tests/fixtures/README.md.