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

cmdk-engine

v0.3.0

Published

The smart command palette engine for React. Built on cmdk. Auto-discover routes, fuzzy search with synonyms, RBAC filtering, frecency ranking, CLI tooling.

Readme

cmdk-engine

The smart command palette engine for React. Built on cmdk. Auto-discover routes, fuzzy search with synonyms, RBAC filtering, frecency ranking, CLI tooling — all in < 5KB.

npm version npm downloads license


Why cmdk-engine?

cmdk gives you beautiful, accessible command menu primitives. But building a production command palette requires more:

| Feature | cmdk | cmdk-engine | |---------|------|-------------| | Composable UI components | Yes | Yes (via cmdk adapter) | | Route auto-discovery | No | Yes — CLI scanner + runtime adapters | | RBAC / permission filtering | No | Yes — any/all modes | | Frecency ranking | No | Yes — exponential decay algorithm | | Keyword synonyms | No | Yes — bidirectional, ranked below direct matches | | Smart route exclusion | No | Yes — auth, error, dynamic routes auto-filtered | | Deterministic sorting | Broken (#264, #375) | Yes — frecency > priority > alphabetical | | First item auto-select | Broken (#280) | Yes — auto-selects on every result update | | Dynamic content updates | Broken (#267) | Yes — reactive pub/sub registry | | CLI tooling | No | Yes — scan, init, validate | | Framework-agnostic core | No | Yes — zero runtime deps |

cmdk-engine owns all filtering (shouldFilter={false}), solving the sorting and selection bugs in cmdk while keeping its composable UI primitives.


Installation

Library (for React projects)

# npm
npm install cmdk-engine cmdk

# bun
bun add cmdk-engine cmdk

# pnpm
pnpm add cmdk-engine cmdk

# yarn
yarn add cmdk-engine cmdk

cmdk and react are peer dependencies.


Quick Start

1. Wrap your app with the provider

import { CommandEngineProvider } from 'cmdk-engine/react'

function App() {
  return (
    <CommandEngineProvider
      config={{
        synonyms: {
          billing: ['money', 'payment', 'credits'],
          settings: ['preferences', 'config', 'options'],
        },
      }}
    >
      <YourApp />
    </CommandEngineProvider>
  )
}

2. Register commands

import { useCommandRegister } from 'cmdk-engine/react'
import { CreditCard } from 'lucide-react'

function BillingPage() {
  useCommandRegister([
    {
      id: 'billing-overview',
      label: 'Billing Overview',
      href: '/billing/overview',
      keywords: ['balance', 'credits'],
      group: 'Billing',
      icon: <CreditCard size={16} />, // React components, strings, or emoji
    },
  ])

  return <div>...</div>
}

3. Use the pre-wired cmdk adapter

import { CommandPalette } from 'cmdk-engine/adapters/cmdk'

function CommandMenu() {
  return (
    <CommandPalette
      dialog
      placeholder="Type a command or search..."
      onSelect={(item) => {
        if (item.href) navigate(item.href)
        if (item.action) item.action(item)
      }}
    />
  )
}

Or use config.onSelect on the provider to handle all selections in one place:

<CommandEngineProvider
  config={{
    onSelect: (item) => {
      if (item.href) navigate(item.href)
      if (item.action) item.action(item)
    },
  }}
>

4. Or build your own UI with hooks

import { useCommandPalette } from 'cmdk-engine/react'

function CustomCommandMenu() {
  const { search, setSearch, groupedResults, isOpen, toggle, select } =
    useCommandPalette()

  return (
    <div>
      <input value={search} onChange={(e) => setSearch(e.target.value)} />
      {groupedResults.map(({ group, items }) => (
        <div key={group.id}>
          <h3>{group.label}</h3>
          {items.map(({ item }) => (
            <button key={item.id} onClick={() => select(item)}>
              {item.icon} {item.label}
            </button>
          ))}
        </div>
      ))}
    </div>
  )
}

select() records frecency, runs onSelect/action/href, and closes the palette — all in one call.


React Router Integration

Auto-discover routes from your React Router config:

import { scanRoutes } from 'cmdk-engine/adapters/react-router'
import { useCommandRegister } from 'cmdk-engine/react'

const commands = scanRoutes(routeConfig)

function App() {
  useCommandRegister(commands)
  return <RouterProvider router={router} />
}

Smart defaults

The scanner automatically:

  • Excludes auth routes/login, /signup, /forgot-password, /oauth/callback, etc.
  • Excludes error pages/404, /500, /error, /not-found
  • Skips dynamic routes/users/:id, /billing/:uuid (can't navigate without a real ID)
  • Derives labels from the path — /billing/overview → "Overview"
  • Derives groups from the first segment — /billing/overview → group "Billing"

Scanner options

const commands = scanRoutes(routeConfig, {
  exclude: ['/admin/*', /^\/debug\//, '/internal'],  // string, glob, or regex
  noDefaultExclude: false,   // set true to skip default auth/error exclusion
  includeDynamic: false,     // set true to include :id routes
})

Route metadata

Enrich routes with metadata using the handle convention:

{
  path: '/billing/overview',
  handle: {
    command: {
      label: 'Billing Dashboard',
      keywords: ['money', 'payment'],
      group: 'Billing',
      icon: <CreditCard size={16} />,
      priority: 10,
    }
  },
  element: <BillingOverview />,
}

Routes with handle.command are always included, even if they have dynamic segments. The scanner also falls back to route.title and route.icon if handle.command doesn't define them.


RBAC / Access Control

Filter commands based on user permissions:

import { createSimpleAccessProvider } from 'cmdk-engine'

<CommandEngineProvider
  config={{
    accessControl: createSimpleAccessProvider(['admin.view', 'billing.read']),
    accessCheckMode: 'any', // user needs ANY listed permission
  }}
>

Commands with permissions: ['admin.view'] will only show for users who have that permission.


Frecency Ranking

Commands you use frequently and recently appear higher in results. No configuration needed — it uses localStorage by default. When you use select(), frecency is recorded automatically.

The algorithm uses exponential decay with a configurable half-life:

score = count * 2^(-timeSinceLastUse / halfLife)

Recent commands

Show a "Recent" group at the top of the palette when the search is empty:

<CommandEngineProvider
  config={{
    frecency: {
      showRecent: true,     // inject "Recent" group when search is empty
      recentCount: 5,       // number of recent items (default: 5)
      recentLabel: 'Recent', // group label (default: "Recent")
    },
  }}
>

CLI Tool

Auto-discover routes and generate sitemaps for your command palette.

Setup

# Initialize config
npx cmdk-engine init

# Scan routes
npx cmdk-engine scan

# Validate config
npx cmdk-engine validate

Config file

// cmdk-engine.config.ts
import { defineConfig } from 'cmdk-engine'

export default defineConfig({
  framework: 'react-router', // or 'nextjs-app', 'nextjs-pages'
  routesDir: './src/routes',
  output: './src/generated/command-routes.json',
  overrides: {
    '/billing': { keywords: ['money', 'payment'], group: 'Billing' },
  },
  exclude: ['/404', '/500', '/_*'],
  synonyms: {
    billing: ['money', 'payment', 'credits'],
  },
})

Pre-commit hook

{
  "husky": {
    "hooks": {
      "pre-commit": "npx cmdk-engine scan && git add src/generated/command-routes.json"
    }
  }
}

GitHub Actions

- run: npx cmdk-engine scan
- run: npx cmdk-engine validate

Architecture

Route Config ─→ Route Adapter ─→ Command Registry ─→ Keyword Engine
                                       │
                                       ├─→ Access Control Filter
                                       │
                                       ├─→ Search Engine (fuzzy / match-sorter)
                                       │
                                       └─→ Frecency Ranking
                                              │
                                              ▼
                                      Headless API / Hooks
                                              │
                                              ▼
                                      UI Adapter (cmdk)

Package Entry Points

| Import | Size | Purpose | |--------|------|---------| | cmdk-engine | ~4KB | Core engine (types, registry, search, keywords, access control, frecency) | | cmdk-engine/react | ~2KB | React hooks (provider, useCommandPalette, useCommandRegister) | | cmdk-engine/adapters/cmdk | ~1KB | Pre-wired cmdk components | | cmdk-engine/adapters/react-router | ~1KB | React Router v6/v7 route scanner | | cmdk-engine/search/match-sorter | ~1KB | Optional match-sorter search backend |

All entry points are tree-shakeable. The core has zero runtime dependencies.


API Reference

Core

import {
  createRegistry,        // Command store (pub/sub, useSyncExternalStore compatible)
  createFuzzySearch,     // Built-in lightweight fuzzy search
  createKeywordEngine,   // Synonym expansion + user aliases
  createAccessFilter,    // RBAC filter (any/all modes)
  createSimpleAccessProvider, // Permission provider from array/Set
  createFrecencyEngine,  // Frecency ranking with exponential decay
  createGroupManager,    // Command group management
  defineConfig,          // Typed config helper for CLI
} from 'cmdk-engine'

React

import {
  CommandEngineProvider, // Context provider
  useCommandPalette,    // Main hook: search + filter + rank
  useCommandRegister,   // Register commands from components
  useFrecency,          // Direct frecency access
} from 'cmdk-engine/react'

Adapters

import { CommandPalette, useCommandPaletteShortcut } from 'cmdk-engine/adapters/cmdk'
import { scanRoutes } from 'cmdk-engine/adapters/react-router'

Key hook return values

const {
  search,          // Current query
  setSearch,       // Update query
  results,         // ScoredItem[] (flat)
  flatResults,     // Same as results
  groupedResults,  // GroupedResult[] — results grouped by group
  groups,          // CommandGroup[] — active groups
  isOpen,          // Palette visibility
  open, close, toggle,
  select,          // Select a command (records frecency + runs handler + closes)
  recordUsage,     // Record frecency manually
} = useCommandPalette()

Type Safety

All types are exported and fully documented:

import type {
  CommandItem,
  CommandRegistry,
  SearchEngine,
  ScoredItem,
  GroupedResult,
  GroupedResults,
  AccessControlProvider,
  FrecencyOptions,
  RecentCommandsConfig,
  CommandGroup,
  SynonymMap,
  RouteCommandMeta,
  CmdkEngineConfig,
  CommandEngineConfig,
  CommandPaletteState,
} from 'cmdk-engine'

cmdk Issues We Solve

| Issue | Description | How We Fix It | |-------|-------------|---------------| | #264 | Sort not restored after clearing search | We own filtering; restore original order when query is empty | | #280 | First item not selected with dynamic content | Auto-select first item after each render cycle | | #375 | Non-deterministic sorting | Deterministic: frecency → priority → alphabetical | | #374 | Scroll position jump on filter | We control the result list; reset scroll on search change | | #267 | Items not updating on async changes | Reactive pub/sub registry; items update immediately |


Contributing

See CONTRIBUTING.md for guidelines.


License

MIT © Priyanshu


If you find cmdk-engine useful, please consider giving it a star on GitHub. It helps others discover the project.