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

@lumen-mirror/cli

v1.0.2

Published

CLI for scaffolding and developing Lumen Mirror smart mirror apps.

Readme

@lumen-mirror/cli

Create and run Lumen Mirror smart mirror apps from the terminal.

Scaffolds projects, generates modules, and runs Vite / SCSS / decorators for you — no vite.config.ts in user apps.

Runtime checklist (via @lumen-mirror/core)

  • Features with shared state + typed events (createFeature / injectFeature)
  • Own signals (signal, computed, fromHttp, …)
  • Templates: {{ }}, @if / @for, (click), [attr.*] bindings, <GroupModule>
  • Security first — allowlisted attribute writes, closed expressions, URL scheme checks
  • Focus mode + typed CSS grid layout
  • Small public API (renderer / binder stay internal)

See the core README for the feature/state/events model.

Install

npm install -g @lumen-mirror/cli

Or use it once without installing:

npx @lumen-mirror/cli new my-mirror

Create a project

lumen new my-mirror
cd my-mirror
npm install
npm run dev

lumen new asks which package manager to use and writes a default config for that choice before install:

| Choice | Config | | ------ | ------ | | npm | .npmrc with engine-strict=true | | pnpm | .npmrc with engine-strict=true and auto-install-peers=true | | yarn | .yarnrc.yml with nodeLinker: node-modules (Vite cannot use Plug'n'Play) |

The scaffold also sets package.json "packageManager" to the installed version, and "engines" to Node 20+. Creating the app through pnpm or yarn skips the prompt and uses that manager.

Open the URL printed in the terminal (default http://localhost:5173). You get a working mirror with four starter widgets on a CSS grid — the clock is the main reference example (alarms, routine profiles, voices, focus mode, and localStorage persistence).

Starter modules

| Widget | What it demonstrates | | ------------- | ----------------------------------------------------------------------------------------------------------------------- | | clock | Reference module: (click), @if / @for, focus mode, trackInterval, nested imports, feature state, localStorage | | weather | HTTP signals, nested tab modules, SVG chart bindings, layout expand / focus | | welcome | Group host — projects Home + Quotes into <GroupModule>; Home listens across features | | news-feed | @for list + events consumed by Welcome Home |

Open the clock gear first — add a routine profile, create an alarm, and pick a voice. Use the welcome ellipsis to swap Home / Quotes.

Electron (optional, dev shell)

Run the same app inside an Electron window during development:

npm run dev:electron

That is lumen serve --electron — Vite + a Chromium window. It is not a packaged installer.

Shipping a kiosk build today:

  1. lumen build → output in dist/ (see lumen.json build.outDir)
  2. Point Electron at the built files with LUMEN_DEV_SERVER_URL unset so the shell loads index.html from the app cwd (typically dist/)

There is no electron-builder / asar pipeline in the CLI yet — treat Electron as a secure browser shell around your Vite build.

Window size and display options live in lumen.json under electron. Security defaults are locked down and iframe-friendly:

| Flag (electron.security.*) | Default | What it does | | ---------------------------- | ------- | ------------------------------------------------------- | | blockWindowOpen | true | Denies window.open / target="_blank" popups | | blockTopLevelNavigation | true | Denies main-frame navigations away from the app | | denyPermissions | true | Denies cam/mic/geo/notification prompts | | allowWebviewTag | false | Chromium <webview> off — prefer <iframe> in modules |

<iframe src="…"> inside a module template is not blocked by these. Opt out only when needed:

"electron": {
  "security": {
    "denyPermissions": false
  }
}

Day-to-day scripts

| Script | What it does | | ---------------------- | ------------------------------------ | | npm run dev | Start the dev server with hot reload | | npm run dev:electron | Dev server + Electron window | | npm run build | Production build to dist/ |

All three delegate to the lumen CLI (serve / build).

Project layout

After lumen new, your app looks like this:

my-mirror/
  lumen.json              # platform config (serve, build, electron)
  index.html
  src/
    main.ts               # bootstraps the mirror
    lumen.config.ts       # typed CSS grid layout (+ optional security)
    global.scss           # base styles
    modules/              # one folder per widget
      MODULE_CONVENTIONS.md
      clock/
      weather/
      welcome/
      news-feed/

src/main.ts

Entry point — loads your layout and modules, then starts the runtime:

import lumenProject from '../lumen.json';
import { bootstrap } from '@lumen-mirror/core';
import appConfig from './lumen.config.js';
import {
  ClockModule,
  WeatherModule,
  WelcomeModule,
  NewsFeedModule,
} from './modules/index.js';

await bootstrap({
  root: lumenProject.root,
  modules: [ClockModule, WeatherModule, WelcomeModule, NewsFeedModule],
  config: appConfig,
});

src/lumen.config.ts

Define where widgets sit on the mirror grid. Rows, columns, and cell names are type-checked:

import { defineLayout, defineLumenConfig } from '@lumen-mirror/core';

export default defineLumenConfig({
  layout: defineLayout(4, 4, [
    ['clock', 'clock', '.', 'weather'],
    ['welcome', 'welcome', 'welcome', 'welcome'],
    ['welcome', 'welcome', 'welcome', 'welcome'],
    ['news-feed', 'news-feed', 'news-feed', 'news-feed'],
  ] as const),
});

Use . for empty cells. Each name must match a module's host.classes entry (e.g. host: { classes: ['clock'] }).

Optional attribute security (replaces the default [attr.*] allowlist):

security: {
  attributeAllowlist: ['href', 'src', 'aria-*', 'data-*', 'viewBox', 'd', 'y1', 'y2'],
},

lumen.json

Platform config the CLI reads — like a slim angular.json. Layout and modules live in TypeScript, not here.

Lumen assumes Electron + modern Chromium. There is no legacy browser target, differential loading, or polyfill pipeline. lumen serve stays fast; lumen build always optimizes unless you opt out.

{
  "name": "my-mirror",
  "root": "#app",
  "index": "index.html",
  "serve": { "port": 5173, "host": "localhost" },
  "build": {
    "target": "esnext",
    "minify": true,
    "sourcemap": false,
    "outDir": "dist"
  },
  "electron": {
    "width": 1080,
    "height": 1920,
    "fullscreen": false,
    "security": {
      "blockWindowOpen": true,
      "blockTopLevelNavigation": true,
      "denyPermissions": true,
      "allowWebviewTag": false
    }
  }
}

| Field | Role | | --------------------------- | -------------------------------------------------------------------- | | root | DOM selector passed to bootstrap | | index | HTML shell Vite uses as the app entry | | serve.port / serve.host | Dev server bind address | | build.target | JS target (esnext by default) | | build.minify | Minify JS + CSS on lumen build (default true) | | build.sourcemap | Emit sourcemaps on lumen build (default false) | | build.outDir | Output folder (default dist) | | electron.* | BrowserWindow options for lumen serve --electron | | electron.security.* | Renderer lockdown (popups / top-level nav / permissions); iframes OK |

Escape hatches for debugging a packaged build: set "minify": false and/or "sourcemap": true. Do not add a vite.config.ts — the CLI owns the bundler config.

Build-time template checks

When Vite transforms a *.lumen.ts file, Lumen reads its templateUrl and validates:

  • control flow (@if / @else / @for / @in)
  • interpolations ({{ ... }})
  • event handlers ((click)="save()", etc.)
  • nested module tags / projection (<GroupModule>…</GroupModule>)

Invalid templates fail lumen build and show in the Vite overlay during lumen serve, with file:line:column so the overlay can jump to the error.

Add a module

From your project root:

lumen generate quotes

This creates a convention-aligned module folder and a barrel export in src/modules/index.ts:

src/modules/quotes/
  quotes.lumen.ts
  quotes.lumen.html
  quotes.lumen.scss
  types/quotes.types.ts
  consts/quotes.consts.ts
  features/quotes.feature.ts
  components/                 # nested child modules

By default it does not add the module to src/main.ts or the grid — use that when the widget is nested inside another module. Put nested modules under the parent's components/ folder, declare them in imports, and use them in the parent template:

import { QuotesModule } from './components/quotes/quotes.lumen.js';

@Module({
  templateUrl: './dashboard.lumen.html',
  styleUrls: ['./dashboard.lumen.scss'],
  imports: [QuotesModule],
  host: { classes: ['dashboard'] },
})
export class DashboardModule extends LumenModule {}
<QuotesModule />

To share one grid cell between several modules, project them into a group (list every member in imports):

<GroupModule>
  <WelcomeHomeModule label="Welcome" />
  <QuotesModule label="Quotes" />
</GroupModule>

Nested modules do not need to be listed in bootstrap({ modules }) — only in the parent's imports.

If you confirm the prompt, generate will also register the module in src/main.ts and src/lumen.config.ts for top-level grid placement.

See src/modules/MODULE_CONVENTIONS.md in a scaffolded app for visibility, timers (trackInterval), groups, and file-layout rules.

Put layout and panel styles on the module host via host.classes — do not repeat that class on a wrapper inside the template (the renderer already applies it to the host element).

Templates use {{ signalName }}, @if, and @for. See @lumen-mirror/core for the full template and signals API.

Commands

| Command | Description | | ------------------------ | ------------------------------------------------ | | lumen new <name> | Scaffold a new mirror app | | lumen generate [name] | Add a module (alias: lumen g) | | lumen serve | Dev server with hot reload | | lumen serve --electron | Dev server + Electron shell (not a packaged app) | | lumen build | Production build |

Requirements

  • Node.js 20+
  • npm, pnpm, or yarn (for installing scaffolded app dependencies)

Learn more