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

@pykero/eyewear-vto

v0.1.0

Published

Browser-based virtual try-on widget for eyewear, rendered at true physical size.

Readme

@pykero/eyewear-vto

Browser-based virtual try-on for eyewear. Frames render at true physical size on the shopper's face, from a webcam, entirely on-device.

Status: 0.1.0 — not shippable to shoppers. Every phase is implemented and unit-tested. No phase gate has been verified, because verification requires recorded fixtures that do not exist yet and a human looking at a camera. See ADR-0012 and docs/phase-status.md. The API is stable enough to build against; the numbers it draws are not yet trustworthy.

Live demo: https://pykero-tryon.pages.dev — open on a phone, tap Start.

Install

npm install @pykero/eyewear-vto@preview three @mediapipe/tasks-vision

@preview is required — nothing is published to latest until a phase gate is verified. three and @mediapipe/tasks-vision are peer dependencies so a host app that already uses three.js does not ship a second copy of it. Changes per version: CHANGELOG.md.

Full integration guide: docs/integration.md — your own frames, self-hosted tracker assets, events, privacy.

How it works: docs/how-it-works.md — the runtime, end to end: the two clocks, the tracker boundary, occlusion, metric scale, lighting.

Use — module

import '@pykero/eyewear-vto'   // registers <eyewear-tryon>
<eyewear-tryon sku="DEV-58"></eyewear-tryon>

Use — plain HTML

<script src="https://cdn.jsdelivr.net/npm/@pykero/[email protected]/dist-lib/eyewear-vto.iife.js"></script>
<eyewear-tryon sku="DEV-58"></eyewear-tryon>

That is the IIFE build (@pykero/eyewear-vto/iife), which bundles three.js for pages with no module resolver; jsDelivr serves it straight from the npm package. See demo.html for the same thing against a local build.

API

| | | |---|---| | start() | Loads the tracker (~5 MB) and opens the camera. Never runs on page load. | | stop() | Releases the camera and tears down the scene. | | sku | Get/set the current SKU. Also settable as an attribute. | | setFrames(frames) | Replace the catalogue. Bare FrameSpecs render procedurally; SkuEntrys carry a glTF modelUrl. Mixed lists are fine. | | assetUrls | Serve the MediaPipe runtime and model from your own origin. Also wasm-base / model-url attributes. | | calibrate() | Show the ID-1 card guide. | | measure() | Take the measurement. Returns PD in mm, or null if rejected. | | clearCalibration() | Delete the stored measurement from this device. | | pdMm | Card-referenced PD, or null when uncalibrated. | | capture(type?) | A still of the try-on as a data URL. |

Events, all CustomEvent and composed: vto-ready, vto-error, vto-tracking-changed, vto-calibrated, vto-calibration-rejected, vto-model-rejected, vto-frame-changed, vto-capture.

Your own frames

el.setFrames([
  { spec: { sku: 'ACME-52', name: 'Acme Round', lensWidthMm: 52, bridgeMm: 18,
            templeLengthMm: 145, lensHeightMm: 42, rimThicknessMm: 3,
            frontWidthMm: 138 },
    modelUrl: '/frames/acme-52.glb' },
])

The millimetres are the render, not metadata. A glTF is measured against them on load and refused if it disagrees — never silently rescaled, because a wrongly-scaled model is internally consistent and would otherwise be undetectable. Failures fall back to the procedural frame and fire vto-model-rejected.

Style the control bar with ::part(controls). Everything else is in a shadow root and closed to host CSS on purpose — product pages have opinionated resets.

Privacy

No video frame, landmark, or derived measurement ever leaves the browser. No network call in this package carries anything derived from the camera. The only thing persisted is the calibration — six numbers in localStorage: head-size ratio, PD, distance, a timestamp and the capture resolution. No image, no landmarks, no embedding, and nothing that identifies a person. clearCalibration() removes it.

capture() returns a data URL to you. This package never uploads it. If your page does, that is your decision to disclose.

Requirements

  • HTTPS, or localhost. getUserMedia requires a secure context.
  • WebGL2 for lens refraction. Without it the lens degrades to a tinted surface.
  • The MediaPipe WASM runtime and model (~5 MB) are fetched on first start(). Defaults are public CDNs; set wasm-base and model-url to serve them from your own origin, which you should do in production.

Publishing

Releases are published by CI, not from a laptop — see publish.yml and ADR-0023. Pushing a v* tag builds, checks the tarball, publishes to npm under the preview dist-tag, then installs the published package into a fresh Vite app and a plain HTML page (IIFE from jsDelivr) and asserts the element renders in headless Chrome.

# 1. bump: package.json "version", VERSION in src/widget/embed/index.ts, CHANGELOG.md
# 2. merge to main, then
git tag v0.1.0 && git push origin v0.1.0

The workflow refuses a tag whose version does not match all three. It needs an NPM_TOKEN repository secret (granular token, publish scope, bypass 2FA).

To dry-run the consumer side locally against an unpublished build:

npm run build:lib && npm pack --ignore-scripts
tools/smoke-registry.sh ./pykero-eyewear-vto-0.1.0.tgz dist-lib/eyewear-vto.iife.js

Why not from a laptop: if ignore-scripts=true is set in your npm config — a common security hardening, and easy to forget — prepack is silently skipped and you publish a package containing nothing but README.md and package.json. It does not error. The workflow builds explicitly and inspects the tarball instead of trusting the hook.

Development

npm run dev        # harness at localhost:5173 (or next free port)
npm test           # unit tests
npm run build:all  # app + ESM + IIFE + declarations
npm run size       # gzipped size vs budget (run build:all first)

npm run size enforces the §10 cap of 5 MB gzipped, plus a per-bundle budget for the ESM build, the IIFE build and the harness entry chunk. CI runs it after both builds and fails on a breach. Raising a budget is a one-line edit to tools/bundle-budget.json — AGENTS.md §8 asks only that the PR say what the extra weight bought.

One thing the number does not include: MediaPipe's ~5 MB WASM runtime and model are fetched from a CDN on the shopper's click and never appear in dist/. The budget measures what this repo ships, not everything a shopper downloads.

Read AGENTS.md before changing anything. It carries ten invariants that are build-breaking rules, not suggestions — the first of which is that frames are never scaled to fit a detected face.