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

@cli-schema/zod

v0.2.0

Published

Derives CLI Schema parameter definitions from Zod schemas (https://github.com/cli-schema/cli-schema)

Readme

@cli-schema/zod

Derives CLI Schema parameter definitions from Zod schemas — types, requiredness, defaults, descriptions, and JSON-Schema validation constraints, all read directly off the schema you already wrote for validation.

Part of the cli-schema-js monorepo. Framework- agnostic: it doesn't know about Commander, yargs, or any other CLI library — it only knows Zod in, structured parameter data out. @cli-schema/commander builds on top of it.

Quickstart

npm install @cli-schema/zod zod
import { z } from 'zod'
import { extractSchemaArgs } from '@cli-schema/zod'

const SearchInput = z.object({
  index: z.string().describe('Index to search'),
  size: z.number().default(10).describe('Number of results'),
})

extractSchemaArgs(SearchInput)
// [
//   { schemaKey: 'index', cliFlag: 'index', type: 'string', required: true,  description: 'Index to search' },
//   { schemaKey: 'size',  cliFlag: 'size',  type: 'number', required: false, description: 'Number of results', defaultValue: 10 },
// ]

That's the core of it: one Zod object schema in, one CLI parameter definition per top-level key out.

Purpose

If your CLI already validates its input with Zod, that schema is a complete, precise description of every flag the command accepts — type, requiredness, default, description, enum/range/length constraints. Re-describing all of that by hand for help text, documentation, or a CLI Schema document is duplicated, drifting work. @cli-schema/zod reads it back out.

It deliberately knows nothing about any particular CLI's own conventions (routing metadata, custom parsing hints, reserved flag names) — those vary per project, so they're exposed as extension points (enrich, a reserved list) rather than baked in.

Features

  • extractSchemaArgs — the main entry point. Walks a z.object()'s top-level shape and returns one SchemaArgDefinition per key:
    • Type inference across string, number, integer (zod's z.int()), boolean, object, array, enum — including through .optional(), .default(), z.lazy(), z.union() (first member wins), z.record()/z.any()/z.unknown() (→ object).
    • Kebab-case flag names derived from the schema key (toKebabCase) — handles snake_case, camelCase, and strips leading underscores (_sourcesource).
    • Requiredness and defaults — a field is required only if it's neither optional nor defaulted; defaultValue is read straight off .default(...).
    • Descriptions from .describe() / .meta({ description }).
    • acceptsArrayForm detection for the union(T, array(T)) pattern — a common way to let a field accept either a single value or a collection while keeping the type inference on the scalar branch for CLI ergonomics.
    • An enrich hook so your own framework can attach extra fields (routing metadata, parse hints, anything) without this package needing to know what they mean.
  • validateSchemaArgs — fail-fast collision detection: throws if two schema keys would produce the same CLI flag, or if a flag collides with your CLI's reserved names.
  • buildFlagKeyMap — a bidirectional cliFlagschemaKey map, for merging parsed CLI flags back into the shape your Zod schema expects.
  • JSON-Schema-derived enrichment (zodToJsonSchema, extractEnumValues, extractElementType, extractValidations) — for the detail extractSchemaArgs alone can't carry (enum values, array element types, range/length/regex/email/url constraints), reusing Zod's own toJSONSchema() rather than re-implementing schema introspection.
  • readMetaField / walkWrapperChain — the low-level primitives the above are built on, exported for CLIs that need their own custom extraction (e.g. a found_in routing tag, or a named-shape check like "is this schema tagged Sort anywhere in its wrapper chain").

Usage

Basic extraction

import { z } from 'zod'
import { extractSchemaArgs } from '@cli-schema/zod'

const args = extractSchemaArgs(z.object({
  numShards: z.number().min(1).max(100),
  level: z.enum(['low', 'medium', 'high']).default('medium'),
  tags: z.array(z.string()).optional(),
}))

args.map((a) => a.cliFlag) // ['num-shards', 'level', 'tags']

Attaching framework-specific extras with enrich

Say your CLI routes some fields to an HTTP path/query/body via .meta({ found_in: '...' }). Rather than this package hardcoding that convention, thread it through enrich:

import { z } from 'zod'
import { extractSchemaArgs, type SchemaArgDefinition } from '@cli-schema/zod'

interface MyExtras {
  foundIn?: 'path' | 'query' | 'body'
}

const schema = z.object({
  index: z.string().meta({ found_in: 'path' }),
  q: z.string().optional().meta({ found_in: 'query' }),
})

const args: Array<SchemaArgDefinition & MyExtras> = extractSchemaArgs(schema, {
  enrich: (field, base) => {
    const foundIn = (field.meta() as Record<string, unknown> | undefined)?.['found_in']
    return foundIn != null ? { foundIn } : {}
  },
})

Validating for flag collisions

import { validateSchemaArgs } from '@cli-schema/zod'

// throws: "help" collides with a name your CLI already uses for --help
validateSchemaArgs(extractSchemaArgs(z.object({ help: z.string() })), ['help', 'json', 'config-file'])

reserved defaults to ['help'] if omitted, since nearly every CLI framework reserves --help.

Round-tripping flags back to schema keys

import { buildFlagKeyMap } from '@cli-schema/zod'

const map = buildFlagKeyMap(args)
map.toSchemaKey.get('num-shards') // 'numShards'
map.toCliFlag.get('numShards')    // 'num-shards'

Enum values, array element types, and validation constraints

extractSchemaArgs alone doesn't carry enum values or range/length constraints — those need the full JSON Schema, which is a separate (and more expensive) step best done once per command:

import { z } from 'zod'
import { extractSchemaArgs, zodToJsonSchema, extractEnumValues, extractElementType, extractValidations } from '@cli-schema/zod'

const schema = z.object({
  level: z.enum(['low', 'medium', 'high']),
  size: z.number().min(1).max(100),
})

const root = zodToJsonSchema(schema)
const properties = root.properties as Record<string, Record<string, unknown>>

for (const arg of extractSchemaArgs(schema)) {
  const node = properties[arg.schemaKey]
  console.log(arg.cliFlag, {
    enumValues: extractEnumValues(node, root),
    elementType: extractElementType(node, root), // only meaningful when arg.type === 'array'
    validations: extractValidations(node, root),  // e.g. [{ kind: 'range', min: '1', max: '100' }]
  })
}

extractValidations covers the constraint kinds directly derivable from JSON Schema keywords — range (minimum/maximum), length (minLength/maxLength), regex (pattern), email and url (format). Kinds that need domain knowledge beyond what JSON Schema expresses (timeSpanRange, count, allowed, denied, uriScheme, fileExtensions, existing, nonExisting, rejectSymbolicLinks — see the spec) aren't inferred automatically; attach them via the enrich hook if your schema encodes them.

Related packages

  • @cli-schema/spec — the Constraint/Parameter types this package's JSON-Schema helpers produce
  • @cli-schema/commander — wires this package's output into a full CliSchema document from a live Commander tree

License

MIT