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

@glassly/miniapp-cli

v0.1.0-dev.1

Published

Dev tools for Glassly miniapps

Readme

@glassly/miniapp-cli (glassly-miniapp)

Author-facing CLI for Glassly miniapps. Pairs with @glassly/miniapp.

glassly-miniapp <command>

Commands at a glance

| Command | What it does | | ------------------------------------------------- | ------------------------------------------------------------------------- | | dev | Starts the dev server with hot reload, prints a QR to load it on a phone | | release | Builds, packs, and serves a QR to install the release on a phone over LAN | | pack | Validates the manifest and zips dist/ into <pkg>-<version>.zip | | manifest | Interactive top-level wizard for editing miniapp.json | | permission list \| add \| remove | Object-verb manifest edits for permissions | | hardware list \| add \| remove | Object-verb manifest edits for hardware requirements | | schema print | Prints the canonical miniapp.json JSON Schema to stdout |

Run with no args to print the same usage table.


dev

glassly-miniapp dev

What it does:

  1. Reads + validates miniapp.json (hard-fails on bad permissions / hardware types so you don't have to debug it on the phone).
  2. Runs the project's build.ts so dist/background/index.js and dist/ui/* are current.
  3. Picks the first free adjacent port pair starting at port: one for static files and the next for the dev sidecar.
  4. Starts a static server that serves miniapp.json, icon.png, and project files.
  5. Starts a dev sidecar on port + 1 — a WebSocket the phone connects to for live reload + console-log forwarding back to your terminal. Failure here is non-fatal; the miniapp still runs without live reload.
  6. Detects the LAN IP, builds a miniapp://dev?url=…&name=…&package=…&dev=<sidecarPort> URL, and prints a terminal QR + the raw URL.
  7. Watches for LAN-IP changes (Wi-Fi switch) every 10s and reprints the QR.

Default port is 3000; override the starting point with a "port": <n> field in miniapp.json. If that port or its sidecar neighbor is busy, dev scans upward until it finds a free adjacent pair.

On the phone: open the Glassly App → Settings → Developer settings → Mini App Development → Scan Mini App QR Code. Phone and laptop must be on the same Wi-Fi.

dev is live and temporary. Keep the CLI and computer running because the Glassly App loads the runtime bundle from that LAN server. Dev miniapps are keyed by their manifest package name, so you can scan and test several side by side; rescanning the same package updates only that entry. The Glassly App caches each entry's name and icon. Use bun run release when you need an installed miniapp that works without the computer.

Ctrl+C stops the server, the sidecar, and the IP watcher.


release

glassly-miniapp release
glassly-miniapp release --no-cache    # force rebuild even if cache is fresh

The all-in-one verb: build a release, pack it, and serve it behind a QR so you can install on as many phones as you like.

Flow:

  1. Validates miniapp.json.
  2. Build cache. Looks for build/<packageName>-<version>.zip. If it exists and every project source file (excluding node_modules, dist, build, .git) is older than the zip, reuses it. Otherwise rebuilds.
  3. Build. Detects your package manager (bun.lockbun, pnpm-lock.yamlpnpm, yarn.lockyarn, else npm) and runs <pm> run build. Your package.json must define a build script that produces dist/.
  4. Pack. Calls the same logic as glassly-miniapp pack — validates the manifest, copies miniapp.json + icon.png into dist/, zips to build/<packageName>-<version>.zip. Prints size + duration.
  5. Serve. Picks a free port between 6789 and 6798. Hosts the bundle, manifest, and icon over HTTP on 0.0.0.0:
    • GET /miniapp.json
    • GET /icon.png
    • GET /bundle.zip
    • GET /__glassly_release/health
  6. Prints a miniapp://release?url=<lan-base>&package=…&version=…&name=… URL + QR.
  7. Stays up so multiple devices can install. Each /bundle.zip fetch logs ✓ Install #N — <name>@<version> → <remote>.

Ctrl+C to stop the server.

On the phone: the Glassly App's QR scanner branches on miniapp://release and uses the dev composer to download + install the bundle. The miniapp lands in lmas/<package>/<version>/ and behaves like any installed local miniapp — runs offline, persists across restarts, no laptop required after install.

Why "release" and not "install": install collides with package managers (bun run install is reserved). Naming the action after the artifact you're producing avoids that collision and matches Android's installRelease mental model.


pack

glassly-miniapp pack
glassly-miniapp pack --no-build    # zip dist/ as-is, skip the build

Produces a distributable ZIP. Use this when you want the artifact only — release calls pack internally.

Steps:

  1. Runs <pm> run build with NODE_ENV=production (same as release), so the zip always contains a production bundle — never a stale dev build left behind by dev. Pass --no-build to skip this and zip whatever is already in dist/.
  2. Verifies dist/ exists.
  3. Validates miniapp.json.
  4. Copies miniapp.json and icon.png into dist/.
  5. Runs the system zip -r command to produce build/<packageName>-<version>.zip and prints the absolute path.

build/ is self-ignoring — the CLI writes a .gitignore containing * into it on creation, so packed zips stay out of version control in any repo without touching the project's own .gitignore.

The resulting ZIP is the artifact you'd upload to the miniapp store.

Requires the zip binary on PATH (preinstalled on macOS and most Linux distros). On Windows, install zip via WSL or use a Unix-like shell.


manifest

glassly-miniapp manifest

Interactive top-level wizard for miniapp.json (Clack-based). Loop:

  • Edit permissions — add, remove
  • Edit hardware requirements — add, remove
  • Show current manifest — pretty-prints the JSON
  • Done — exits

Persists after every confirmed change, so Ctrl+C never loses a saved edit.

The wizard shares its mutation backend (manifest-mutate.ts) with the object-verb commands below — behavior and validation are identical.


permission

glassly-miniapp permission list
glassly-miniapp permission add [TYPE]
glassly-miniapp permission remove [TYPE]

add / remove are interactive when called without TYPE (Clack select prompts) and non-interactive when TYPE is provided.

Allowed TYPE values: MICROPHONE, CAMERA, CALENDAR, LOCATION, BACKGROUND_LOCATION, READ_NOTIFICATIONS, POST_NOTIFICATIONS.

Adding a permission interactively prompts for an optional human-readable description (shown in the OS prompt when the user is asked to grant the permission).


hardware

glassly-miniapp hardware list
glassly-miniapp hardware add [TYPE] [LEVEL]
glassly-miniapp hardware remove [TYPE]

Allowed TYPE values: CAMERA, DISPLAY, MICROPHONE, SPEAKER, IMU, BUTTON, LIGHT, WIFI. Allowed LEVEL values: REQUIRED, OPTIONAL.

  • REQUIRED — glasses without this hardware can't run the app (hidden in the store / launcher on incompatible devices).
  • OPTIONAL — glasses without this hardware still run the app, in a degraded state.

Add is interactive when called without TYPE / LEVEL. Non-interactive form requires both.

The EXIST hardware type is injected by the phone at runtime (every miniapp implicitly requires that glasses are present). It's intentionally not in the allowed-types list — don't declare it.


schema

glassly-miniapp schema print

Prints the canonical miniapp.json JSON Schema to stdout. Useful for piping into IDE config or for validation in CI.

The schema is generated from the same constants the validator uses (ALLOWED_PERMISSIONS, ALLOWED_HARDWARE_TYPES, ALLOWED_HARDWARE_LEVELS), so it can never drift from validation behavior.

The published schema file ships at node_modules/@glassly/miniapp-cli/schema/miniapp.schema.json for editors that read $schema from miniapp.json. The scaffolder (create-miniapp) injects this $schema line into new projects automatically.

glassly-miniapp schema regenerate exists too but is a CLI-internal command — it rewrites the published schema file from the in-source allowed-values lists. Authors don't need it.


miniapp.json shape

{
  "$schema": "./node_modules/@glassly/miniapp-cli/schema/miniapp.schema.json",
  "packageName": "com.glassly.example",
  "version": "1.0.0",
  "name": "Glassly Example",
  "description": "…",
  "icon": "icon.png",
  "port": 3000,
  "permissions": [{"type": "MICROPHONE", "description": "Listen for what to caption."}],
  "hardwareRequirements": [
    {"type": "DISPLAY", "level": "REQUIRED"},
    {"type": "MICROPHONE", "level": "REQUIRED"}
  ]
}

Required: packageName, version, name, hardwareRequirements. Everything else is optional.

packageName must be reverse-DNS (^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$).

port defaults to 3000 for dev and is ignored by release (which picks its own free port). For dev, this is the starting port; if the port or its sidecar neighbor is busy, the CLI scans upward until it finds a free adjacent pair.

The CLI's allowed-value lists are mirrored by hand from @glassly/types to keep the CLI dependency-light so bunx glassly-miniapp stays fast. Drift between the two is caught at validation time, not import time.


File map

  • Subcommand handlers: src/{dev,release,pack,permission,hardware,schema,manifest-wizard}.ts
  • Manifest validation + allowed-value lists: src/manifest.ts
  • Manifest mutation backend (shared by wizard + object-verb commands): src/manifest-mutate.ts
  • Manifest read/write helpers: src/manifest-format.ts
  • Permission/hardware human-readable hints: src/permission-hints.ts
  • Dev sidecar WebSocket server: src/dev-server.ts
  • QR rendering: src/qr.ts
  • Generated JSON Schema: schema/miniapp.schema.json (regenerated via schema regenerate)