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

@productmakersro/kit

v0.1.0

Published

Community CLI for the Product Makers design system kit.

Readme

@productmakersro/kit

The community CLI for the Product Makers design system. Plain Node ESM, requires Node 20+. Runtime dependencies: @productmakersro/fidelity-engine and an exact-pinned shadcn (see "kit add delegates to shadcn" below).

npx @productmakersro/kit <command>

Status

Every command in the table below is implemented for real — no stubs remain.

Commands

| Command | Does | Status | | ----------------------- | ------------------------------------------------------------ | ------------------------ | | kit init | Create kit.config.json + components.json, vendor pm-guidance/pm-tokens | implemented | | kit add <item...> | Add one or more items from the registry to this project | implemented | | kit list | List every item available in the registry | implemented | | kit search <query> | Search the registry for items matching a query | implemented | | kit view <item> | Show details for a single registry item | implemented | | kit render <file> | Render a canvas HTML file to a PNG through the fidelity engine | implemented | | kit verify <lock\|project> | Verify a design-lock.json (or a project that has one) against the fidelity engine | implemented | | kit doctor | Check this machine and project for problems kit needs fixed | implemented | | kit update [item...] | Report/apply upstream updates for installed items, without ever clobbering a local edit | implemented |

Global flags on every command:

  • --json — machine-readable output instead of human text
  • --help, -h — show help (kit <command> --help for a specific command)
  • --version, -v — print the installed version
  • --registry-url <url> — override the registry to talk to (init, add, list, search, view, update); without it, these commands read registryUrl from kit.config.json (written by kit init) and fail with a "run kit init" error if neither is set
  • --dry-run — (add, update) resolve and print the plan; write nothing

kit init

Writes two files in the current directory:

  • kit.config.json — { "registryUrl": "..." } (plus a fonts array once something vendors a font — see kit add below). --registry-url overrides the default (a placeholder constant until the registry host is actually deployed — see src/registry-url.mjs); with no flag and a real TTY, you're prompted once with that default; non-interactively (scripts, CI) the default is used silently, no hang.
  • components.json — the config the pinned shadcn CLI needs to resolve @productmakersro/<item> against this registry (a namespaced registries entry pointing at <registryUrl>/{name}.json). If a components.json already exists (a project that uses shadcn for its own UI too), only the @productmakersro registry entry is added — nothing else in the file is touched.

Then installs the pm-guidance and pm-tokens items through the exact same path kit add uses. If that install fails, kit init still reports the two config files it already wrote and tells you to re-run kit add pm-guidance pm-tokens.

kit add delegates to shadcn

kit add <item...> does not place files itself — it wraps the pinned shadcn CLI (4.19.0, exact — see DECISIONS.md):

  1. Pre-flight — fetches the registry index itself and confirms every requested item exists (a closest-match suggestion when one doesn't), then resolves the requested items' full local dependency tree (registryDependencies refs namespaced @productmakersro/...) so it knows every file target and font asset before touching disk.
  2. --dry-run stops here: prints the resolved target list, writes nothing, never invokes shadcn.
  3. Delegate — spawns the pinned shadcn's own add command (-y, deliberately never -o/--overwrite) to do the actual file placement.
  4. Font binary channel — shadcn's installer only ever writes files[] entries that carry inline content; a font's real bytes never do (see DECISIONS.md), so kit add separately fetches every meta.fonts[] asset the resolved tree declares, verifies its sha256 (and byte length, if given) before writing anything, and writes the verified bytes as binary — never through shadcn's text writer. Fonts vendored this way are recorded into kit.config.json's fonts array (the same shape kit doctor's fonts check reads).
  5. Post-verify — every declared file target (code files with content, plus every font just fetched) must exist on disk afterward. shadcn's own conflict handling (an existing, differently-content file) makes it auto-answer "No" and abort without writing the rest of that run's files — kit add never passes -o, so it never silently overwrites, and post-verify turns that abort into an honest non-zero exit instead of a false "exit 0, files missing".

kit add never reports success on a partial install: item-not-found, a dead/HTML-responding registry, a delegate failure, a font sha256 mismatch, or a post-verify miss all exit non-zero with a message naming what happened.

kit list / kit search <query> / kit view <item>

Read-only. list/search read <registryUrl>/registry.json; view additionally fetches <registryUrl>/<item>.json for the full item (files, dependencies, meta). All three accept --registry-url to bypass kit.config.json. Same network-error contract as kit doctor's registry-reachable check: 5s timeout, HTML-as-200 detected, a dead host fails fast with a clear message.

kit render wraps the fidelity engine's pm-export

kit render <file> [--format linkedin|luma|stories|badge-a6] [--out <png>]
           [--width <px> --height <px>] [--scale <n>] [--timeout <ms>]

Resolves @productmakersro/fidelity-engine's installed pm-export bin (the same programmatic require.resolve this CLI already uses for shadcn — no PATH lookup, works fully offline once kit is installed) and spawns it. This command never reinterprets the engine's exit code — what pm-export returns (its own small scheme: 0 pass, 1 render failure, 2 usage/setup error — see @productmakersro/fidelity-engine's README) is exactly what kit render returns, except for the CLI's own --timeout kill (below).

  • Guardrails kit itself enforces, before ever touching the engine: <file> must exist, must not be a directory, and must have a .html/ .htm extension — any of those is a usage error (2). Dimensions and scale are not second-guessed here (a 20000×20000 canvas is a legitimate, if slow, request) — they pass straight through to pm-export.
  • --format looks up named dimensions: linkedin (1080×1350), luma (1080×1080), stories/story (1080×1920), badge-a6 (1311×1818 — a print object, at 300dpi with a 3mm bleed). These mirror ProductMakersStudio's own social-format picker exactly (there is no a4 preset anywhere in the source system — its one print format is an A6 badge, not A4). A project's own pm/tokens/design-lock.json (the file kit add pm-tokens installs) can override any of these names with a top-level formatPresets: { name: { width, height } } object if one ever declares it — checked first, falling back to the builtin table. Explicit --width/--height (always required together) override --format when both are given.
  • --timeout (default 120000ms) bounds the whole pm-export child's wall clock — an absurd canvas (huge dimensions, a 40MB HTML with thousands of inline images, a page that never settles) is killed (SIGTERM, then SIGKILL after a grace period) rather than hanging kit render forever. Whether a run actually timed out is decided by a timedOut flag set the moment kit starts that kill escalation — never inferred from how the child died, because that's ambiguous both ways: a child that catches the SIGTERM fallout can still exit with a plain status and no signal (would silently launder a real timeout into a generic failure), and a child killed by something else entirely (OOM killer, a stray manual kill) exits with a signal despite never having overrun the budget (would invent a timeout that never happened). A real --timeout kill is reported as pm-export's own exit 1 ("render failure") with a message naming the timeout and how to raise it; a child killed by an unrelated signal is reported as the same exit 1 but with a message naming the signal and stating plainly that it ran well within the timeout budget — no timeout language, no false remediation.
  • No Chromium/Playwright, or the wrong pinned build: pm-export's own pre-flight chromium-pin guard (identical to pm-verify's — see @productmakersro/fidelity-engine's README) exits 2 or 4 with the exact install/fix command, passed through untouched.

kit verify wraps the fidelity engine's pm-verify

kit verify <lock|project> [--screen <id>] [--calibrate] [--src <dir>] [--timeout <ms>]

<lock|project> is either a design-lock.json path directly, or a project directory — resolved against the one place kit add pm-tokens actually installs it (pm/tokens/design-lock.json), falling back to a bare design-lock.json at the directory's root. Neither found is a usage error (2) naming both paths it checked.

Exit codes 0-5 are passed through VERBATIM — this command maps nothing (see the engine's own exit-code table below). --screen, --calibrate, and --src forward straight to pm-verify's matching flags. --timeout (default 600000ms — ten minutes, since a lock can declare many screens and the engine's own render child already budgets up to 180s per screen) kills a hung pm-verify and reports it as the engine's own exit 5 ("render failure/timeout") — the same bucket the engine would use for the same real-world condition, never a code this wrapper invents. Whether a run actually timed out is decided by a timedOut flag set the moment kit starts the kill escalation, never inferred from how the child died (see kit render's --timeout paragraph above for why that inference is unreliable in both directions). A child killed by a signal kit never sent — a crash, not a timeout — is also reported as exit 5 (same "render failure/environment" bucket) but with timedOut: false, the real signal name, and no timeout language.

kit doctor

Runs eight checks, every one of them for real, and never stops early — a FAIL on one check doesn't skip the rest:

| Check | What it does | | --------------------- | ---------------------------------------------------------------------------------------------- | | node-version | Node >= 20. | | disk-space | Free space in the current directory (portable fs.statfs, no df spawn). FAIL under 500MB, WARN under 2GB. | | cwd-writable | The current directory is writable. | | config-present | Whether kit.config.json exists above the current directory and, if it does, that its top level is a JSON object. Absence is WARN, not FAIL — run kit init to create one. A file that exists but isn't a usable object is FAIL. | | registry-reachable | If kit.config.json sets registryUrl, fetches it (5s timeout) and confirms it answers JSON. A server that answers HTML (a homepage, a dead deploy's error page, a misrouted path) is a distinct failure: "got HTML, expected JSON; is the URL right?". No registryUrl configured is PASS — it's optional until you set one. The value is only ever echoed with control characters (e.g. ANSI escapes) stripped, never interpolated raw. If kit.config.json exists but couldn't be evaluated (invalid JSON, non-object top level, unreadable) this is WARN — "could not be evaluated" — never the "no registryUrl configured" PASS, since a field the file can't be parsed for might still be set in it. | | chromium-pin | Delegates to @productmakersro/fidelity-engine's own readiness gate (pm-setup-check) to report the resolved Chromium build against the pinned build read from that package's vendor/engine.config.json. render/verify are optional for colleagues who only compose kit items, so a missing fidelity-engine install, missing Playwright, or a build mismatch all degrade to WARN with the exact command to fix it — including pm-setup-check's own [MISSING] ... expected at: <path> line and, when PLAYWRIGHT_BROWSERS_PATH is set, that value and a hint to unset it if it points at a stale/foreign cache, so two different broken-install causes never produce byte-identical output. The one case that FAILs is an unreadable/corrupt vendor/engine.config.json: kit doctor never falls back to a guessed build — that would risk a false PASS while pm-verify itself exits non-zero on the same corruption. | | fonts | If kit.config.json declares fonts: [{ family, files: [...] }] (the same shape the fidelity engine's own design-lock uses — kit add's font binary channel writes it), confirms every declared file exists on disk. No fonts field, or no kit.config.json yet, is PASS/WARN respectively — nothing to check until kit init/kit add vendor something. Same as registry-reachable above: a kit.config.json that exists but couldn't be evaluated is WARN — "could not be evaluated" — never the "no kit.config.json found" WARN. | | engine-vendor-integrity | Verifies @productmakersro/fidelity-engine's own vendor/ tree against its vendor/CHECKSUMS.sha256 manifest (offline, no Chromium, ~200ms — the same check as that package's pm-verify --check-vendor). FAILs with the changed/missing/extra files named if the vendor tree has been hand-edited or corrupted since install; a missing fidelity-engine install is WARN, same as chromium-pin. |

Every check reports one of PASS, WARN, or FAIL, plus a detail line and a remediation line (empty when nothing needs doing). kit doctor exits 0 when every check is PASS or WARN, and 1 when any check is FAIL — a FAIL always means a real, actionable problem, never a "not built yet" placeholder. Each check runs with its own timeout, so one hung network call or child process can't stall the others — a timed-out check reports WARN rather than hanging kit doctor forever.

kit doctor --json returns { ok, checks: [{ check, status, detail, remediation }, ...] }.

kit update

kit update [item...] [--check] [--dry-run] [--force] [--keep-local] [--registry-url <url>]

Every kit add/kit init install records what it wrote into .kit/manifest.json: per item, a content-derived version, an installedAt timestamp, and a target -> sha256 map of every file (and font) that item declared. kit update reads that manifest, refetches each item's current registry JSON, and puts every item into exactly one of four states:

| State | Meaning | | --------------------- | ------------------------------------------------------------------------ | | up-to-date | Local files match the manifest; upstream is unchanged. | | update-available | Local files match the manifest; upstream changed — safe to reinstall. | | locally-modified | One or more of the item's files differ from what the manifest recorded (edited, deleted, or an untracked collision — see below) — never touched without --force. | | missing-upstream | The item no longer exists in the registry at all — local files are kept, untouched. |

Untracked-collision detection. locally-modified isn't driven only by the manifest's own recorded file set — it's cross-checked against the full set of targets the item's CURRENT upstream JSON declares (the exact set kit update is about to write). Any upstream target that (a) this item is about to write and (b) the manifest never recorded for it, but which already has a real file sitting at that path on disk, is treated exactly like any other locally-modified file: reported by name, refused without --force, and backed up first when --force is given. This closes three routes a target could otherwise reach the writer with no hash check at all: upstream adding a file whose target collides with a file the user already had (never installed by kit); upstream dropping a file and a LATER version re-adding the same target, after an intervening update already stopped tracking it; and a truncated/partial manifest entry (files: {}) that would otherwise report zero targets to check even though real files sit on disk. A target with nothing on disk yet is never a collision — it's simply a new file the upgrade is free to create.

files-removed-upstream. When the current upstream item no longer declares a target the manifest DOES have recorded, that target is never deleted — same "never touch a file kit didn't write" rule as missing-upstream — but a real (non-dry-run) update does stop tracking it in the item's next manifest entry (that item's own record no longer recognizes it as one of its files). This is reported explicitly, every time it's still true of the manifest being evaluated: an explicit filesRemovedUpstream: [...] array on every item in both --check and plain/--dry-run --json output, and a [files-removed-upstream: ...] suffix in the human-readable line — never silent. If the registry later re-adds the same target, the untracked-collision check above is what protects whatever the user did with the file in the meantime.

kit update --check never writes anything, regardless of what it finds — it's the staleness report. kit update (no --check) applies what it safely can: up-to-date and missing-upstream items are left alone; update-available items are reinstalled and the manifest updated; locally-modified items are refused by default (reported, files untouched) unless --force is given, in which case every locally-modified file for that item is first copied to <file>.backup-<8-hex-sha> (the backup's bytes are exactly what was on disk before the overwrite), then the item is reinstalled and the manifest updated. --keep-local explicitly skips locally-modified items (same effect as the default, worded as an acknowledgement rather than a warning). --force and --keep-local are mutually exclusive; --check cannot be combined with --dry-run/ --force/--keep-local — it never writes on its own. --dry-run shows the exact same plan (including the backup path a force-upgrade would create) without writing anything. With no item... given, every item in the manifest is in scope; naming specific items restricts the run to those (and fails clearly if one was never installed).

Reinstalling an item writes ONLY that item's own declared files (content files verbatim, font assets through the same fetch+sha256-verify+write path kit add's font channel uses) — never a dependency's files, even if the item's registryDependencies names one. This is a deliberate difference from kit add's delegated-to-shadcn install path: shadcn add always re-resolves and re-writes the FULL dependency tree, which for kit update would risk silently overwriting a colleague's local edit to a DIFFERENT, unrelated item just because it happened to be a dependency of the one item being updated. Every item in this registry is registry:item/ registry:file with literal inline content (see item-tree.mjs's collectFileTargets comment) — never the registry:component/ui/block types that trigger shadcn's import-alias/AST rewrite — so writing file.content directly produces the identical on-disk result shadcn add would, scoped correctly.

Config

kit.config.json is discovered by walking upward from the current directory. Its absence is tolerated everywhere — kit init creates one. The two fields it holds are registryUrl (a string, written by kit init, read by add/list/search/view when --registry-url isn't passed) and fonts (an array of { family, files: [...] }, files relative to kit.config.json's own directory — the same shape the fidelity engine's design-lock uses for vendored fonts; appended to by kit add's font binary channel).

A file that exists must be a JSON object at the top level, and if it sets registryUrl or fonts, those fields must match the shapes above — anything else (an array/bare string/number/null top level, a non-string registryUrl, a non-array fonts, or a fonts entry missing files) is reported as a real problem by kit doctor (see below), not silently accepted.

Flags never take an inline value

--json, --help/-h, --version/-v, --dry-run, --calibrate, --check, --force, and --keep-local are plain boolean switches. --json=false (or =anything) is a usage error (exit 2), not a silent way to turn JSON off — always pass the flag on its own. Every other flag (--registry-url, --format, --out, --width, --height, --scale, --timeout, --screen, --src) takes a value (--flag value or --flag=value); passing any flag to a command that doesn't use it (kit doctor --registry-url ..., kit list --format ..., kit verify --width ...) is a usage error naming which command was given and which flag it doesn't take. kit update additionally rejects --check combined with --dry-run/--force/--keep-local, and --force combined with --keep-local — both are usage errors, not silently-resolved conflicts.

Exit codes

| Code | Meaning | | ---- | --------------------------------------------------------------------- | | 0 | Success | | 1 | Generic failure (e.g. kit doctor found a real problem); also kit render's own "render failure" bucket (including a --timeout kill) | | 2 | Usage error — bad command, bad flag, wrong number of arguments; also kit render/kit verify's "setup/usage error" bucket (e.g. no Chromium/Playwright installed) | | 3–5 | kit verify only — passed through VERBATIM from @productmakersro/fidelity-engine's pm-verify (its own exit-code table: 3 DIMENSION_MISMATCH, 4 FONT_PARITY or an unconfirmed Chromium pin, 5 render failure/timeout, including a kit verify --timeout kill) | | 64 | Not implemented — the command exists but its logic ships in a later wave |

kit render and kit verify are the two exceptions to "every other command returns 0 on success, non-zero on failure": their real exit code is whatever the delegated fidelity-engine binary returned (see each command's own section above) — a non-zero code from either can mean a legitimate finding (a real fidelity/lint failure, DIMENSION_MISMATCH, FONT_PARITY), not that kit itself failed to run the command.

On Node < 20, kit prints one line to stderr and exits 1 before doing anything else — no stack trace, regardless of command.

If kit's own files can't be loaded at all (a corrupted or partial install — e.g. a missing or syntactically broken file under src/), bin/kit.mjs catches that too and prints one line telling you to reinstall, exiting 1 — never a raw Node stack trace, even when the failure is in kit's own loading code rather than in a command.

Error contract

Every error goes to stderr as one human-readable line:

kit: unknown command 'lst'. Did you mean 'list'?

With --json, the same failure is a single structured object on stderr instead:

{ "error": { "code": "USAGE_ERROR", "message": "unknown command 'lst'.", "suggestion": "list" } }

Error codes: USAGE_ERROR, NOT_IMPLEMENTED, INTERNAL_ERROR (V1-C2); REGISTRY_UNREACHABLE, REGISTRY_HTML_RESPONSE, REGISTRY_HTTP_ERROR, REGISTRY_INVALID_JSON, REGISTRY_MALFORMED, REGISTRY_NOT_CONFIGURED, ITEM_NOT_FOUND, CONFIG_INVALID, CONFIG_WRITE_FAILED, SHADCN_NOT_FOUND, SHADCN_VERSION_MISMATCH, INSTALL_FAILED, INSTALL_VERIFY_FAILED, FONT_VERIFY_FAILED, FONT_TARGET_UNSAFE, INIT_BOOTSTRAP_FAILED (V1-C3, init/add/list/search/view); ENGINE_NOT_FOUND, RENDER_LAUNCH_FAILED, VERIFY_LAUNCH_FAILED (V1-C5, render/verify — a broken/partial @productmakersro/fidelity-engine install; distinct from a real engine exit code, which is not a KitError at all — see "Exit codes" above); MANIFEST_READ_FAILED, MANIFEST_INVALID, NOTHING_INSTALLED, ITEM_NOT_INSTALLED, UPDATE_CHECK_FAILED (V1-C8, update). Unknown commands and unknown flags both exit 2 with a closest-match suggestion when one is close enough to guess.

bin/kit.mjs sets process.exitCode rather than calling process.exit(), so Node drains any output still buffered for a piped stdout/stderr before exiting — large --json payloads (a future wave's kit list --json | jq, for example) are not silently truncated.

Tests

npm test

Runs the node:test suite in test/, which drives the real bin/kit.mjs as a child process (not internal unit calls) and checks: version/help, the uniform not-implemented contract (human + --json), kit doctor's real checks, usage-error exit codes and suggestions, that no raw stack trace ever reaches stderr, the Node < 20 guard, and a regression test that pushes a >64KiB error message through a piped stderr to confirm it survives intact.

The init/add/list/search/view section serves test/fixtures/ registry/ (3 items, schema-valid per node tools/registry-lint.mjs --registry test/fixtures/registry) over a real local HTTP server and exercises the REAL pinned shadcn CLI end to end — no mocking — covering: happy-path init/add (files + the font binary channel, sha256-verified), --dry-run writing nothing, an offline/dead registry, an HTML-as-200 registry, item-not-found, a foreign registry namespace, and a pre-existing conflicting file causing shadcn's own install to abort mid-run (verified live against the pinned CLI) — which kit add's post-verify step turns into a clean non-zero exit instead of a false success.

kit render/kit verify's CLI-subprocess-level coverage (argv parsing, --help, cross-command flag rejection, guardrails that reject before the engine is ever touched) lives in this same file's "kit render / kit verify (V1-C5)" section, following the same real-subprocess style as everything else here. Argument-to-engine-args mapping and exit-code (0-5) passthrough are covered separately in test/render.test.mjs and test/verify.test.mjs, at the mock level (runRender/runVerify called directly, engine binary swapped for a shim script via binPathOverride) — those files' own header comments explain why a real PATH-based engine-bin shim can't be landed from a bin/kit.mjs subprocess in this workspace (the real @productmakersro/fidelity-engine package always wins node's ordinary node_modules resolution walk before NODE_PATH is ever consulted, verified empirically). Both suites also carry one real end-to-end smoke test each (kit render/pm-export against a real installed Chromium) — conditional, not skipped by default: they only skip when the pinned Chromium build genuinely isn't cached on the machine running the tests, and never trigger a download themselves.

kit update's (V1-C8) install-then-update flow lives in test/update.test.mjs, against its own fixture registry (test/fixtures/ update-registry/) rather than reusing test/fixtures/registry/ — one dependency-free item (widget) so every write a test asserts on is unambiguously that one item's own file, plus a v2 snapshot with the item's content bumped and a v3-missing snapshot with an empty items[] (the four --check states: up-to-date, update-available, locally-modified — including the "deleted locally" case — and missing-upstream). Covers the full apply path too: a safe upgrade with no local edits, a locally-modified item refused by default and by --keep-local (file provably untouched byte-for-byte), --force (the backup file's bytes proven equal to the edited content that was overwritten, then a follow-up --check confirming up-to-date), and --dry-run (directory listing AND every file's mtime proven unchanged — not just existence).

A round-3 adversarial-critic gap (the manifest's own recorded file set driving locally-modified with no cross-check against what an upgrade is actually about to write) is covered by a dedicated describe block against two more fixture items (grower, shrinker) plus a v3-readd snapshot: upstream ADDING a file whose target collides with a hand-written file the user already had; upstream DROPPING a file (--check/--json both report it via filesRemovedUpstream, the file is kept on disk, untracked) and a LATER version re-adding the same target (refused without --force, backed up with --force, even though the manifest had already forgotten it); and a truncated manifest entry (files: {}) still protecting the real, locally-edited file on disk instead of reporting zero targets to check.