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

explainshell-wasm

v0.5.0

Published

explainshell compiled to WebAssembly with TypeScript API

Readme

explainshell-wasm

explainshell compiled to WebAssembly, with a typed TypeScript API for Node.js and browsers.

The module is built with Rust (wasm32-unknown-unknown) via wasm-bindgen, exposing the Explainshell class (new(data), explain(command), manpage_count()). The manpage corpus is exported from explainshell's SQLite database to a MessagePack bundle at build time; both runtimes load the same artifacts.

Requirements

  • Node.js >= 20
  • Rust via rvm-windows (rvm use stable), with the wasm32-unknown-unknown target
  • wasm-bindgen-cli matching the wasm-bindgen crate version (see Cargo.lock)
  • Python 3 + msgpack (data export only)
  • The prebuilt dist/ artifacts (see Building), or build them yourself

Installation

npm install explainshell-wasm

Data notice

Explaining commands requires the manpage database. Since v0.1.1, the build automatically downloads the full database from explainshell's GitHub releases (the db-latest tag, ~20-30 MB MessagePack bundle covering thousands of manpages from Ubuntu/Arch). No manual setup required.

Data source priority (first match wins):

  1. $EXPLAINSHELL_DB environment variable — explicit path to a local explainshell.db
  2. ./explainshell.db in the repo root — local database file
  3. .explainshell-cache/ — cached auto-downloaded database (gitignored)
  4. Auto-download from GitHub releases — fetches newest explainshell-*.db.zst, verifies SHA256, decompresses with zstd-wasm
  5. CI fixture fallback (test/fixtures/test.db) — 4 manpages only, with loud warning

To force the CI fixture (e.g., for minimal test builds):

EXPLAINSHELL_DB= npm run build  # empty value skips auto-download

License note: The database contains upstream manpage text (GPL, BSD, MIT, etc.). See LICENSE-DATABASE.md for redistribution terms. The source field in each entry identifies the originating package.

Publishing

One-step release:

npm run release-patch   # 0.1.0 -> 0.1.1
npm run release-minor   # 0.1.0 -> 0.2.0
npm run release-major   # 0.1.0 -> 1.0.0

This runs tests, bumps the version, rebuilds dist/, pushes the tag, and publishes to npm.

For manual control:

npm run build
npm publish

Usage

Node.js

import { createExplainshell, explain } from 'explainshell-wasm'

// One-shot
const result = await explain('tar -xvf archive.tar')

// Reusable instance (WASM module is instantiated once and reused)
const explainshell = await createExplainshell()
console.log('manpages:', explainshell.manpageCount())
const result2 = await explainshell.explain('sudo tar -xvf archive.tar')

By default the loader resolves dist/wasm-node/explainshell.js and dist/explainshell.data.msgpack relative to the package. Pass explicit paths when needed:

const explainshell = await createExplainshell({
  dataPath: '/path/to/explainshell.data.msgpack',
})

Browser

Serve dist/wasm-web/ and dist/explainshell.data.msgpack from the same origin with correct MIME types (application/wasm for .wasm, text/javascript for .js, application/octet-stream for .msgpack), then:

import { createExplainshell } from 'explainshell-wasm'

const explainshell = await createExplainshell({
  runtime: 'browser',
  glueUrl: '/wasm-web/explainshell.js',
  wasmUrl: '/wasm-web/explainshell_bg.wasm',
  dataUrl: '/explainshell.data.msgpack',
})
const result = await explainshell.explain(editorValue)
console.log(result)

API reference

createExplainshell(options?)

Creates (or returns the cached) explainshell instance, instantiating the WASM module on first call. Pass forceNew: true to bypass the cache.

await createExplainshell(options?: {
  gluePath?: string;             // node: path of the wasm-bindgen glue
  dataPath?: string;             // node: path of the .data.msgpack bundle
  glueUrl?: string;              // browser: URL of the wasm-bindgen glue
  wasmUrl?: string;              // browser: URL of the _bg.wasm module
  dataUrl?: string;              // browser: URL of the .data.msgpack bundle
  runtime?: 'node' | 'browser' | 'auto';  // default 'auto'
  forceNew?: boolean;            // bypass cache (default false)
});

explain(command, options?)

await explain('git commit -m "msg"') // ExplainResult

resetExplainshell()

Terminates the cached instance. Mainly useful in tests.

ExplainOptions

interface ExplainOptions {
  distro?: string // reserved for multi-distro bundles
  release?: string // reserved for multi-distro bundles
}

ExplainResult

interface ExplainResult {
  groups: MatchGroup[] // one "shell" group plus one per command
  expansions: Expansion[]
}

interface MatchGroup {
  name: string // e.g. "shell", "command1"
  results: MatchResult[]
  manpage?: ParsedManpage | null
  suggestions?: ParsedManpage[]
  error?: string | null
  positional_index: number
}

interface MatchResult {
  start: number
  end: number
  text?: string | null // help text, null when unknown
  match_text?: string | null // matched input span
  debug_info?: Record<string, unknown> | null
}

How it works

explainshell.db (SQLite, built by the explainshell pipeline)
  │  scripts/export_wasm_data.py (msgpack)
  ▼
dist/explainshell.data.msgpack  (versioned bundle: manpages + mappings)
  │  crates/{core,data,parse,match} (Rust)
  │  cargo build --target wasm32-unknown-unknown
  │  wasm-bindgen --target {nodejs,web}
  ▼
dist/wasm-{node,web}/explainshell.js (+ _bg.wasm)
  │  src/runtime/{node,browser}.ts
  ▼
  explain(command) → Promise<ExplainResult>

Key design points:

  • Single entry point. Explainshell.explain() takes a command string and returns ExplainResult JSON. The shell parser (bashlex port) and matcher run entirely inside the module.
  • In-memory data. Manpage lookup resolves against the bundled MessagePack data; there is no host filesystem or network access from WASM. Repeated calls share no mutable state.
  • No WASI. The module is pure computation on wasm32-unknown-unknown, so both runtimes use the stock wasm-bindgen glue with no WASI shim.
  • Cache discipline. createExplainshell caches by artifact path; forceNew: true bypasses it.

Building

Requires Rust (see Requirements). Python is needed only for the data export step.

# Toolchain setup (Windows, one time)
# install https://github.com/MemoryClear/rvm-windows, then:
rvm use stable
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.128 --locked
pip install msgpack

npm run build:wasm   # Rust -> wasm-bindgen -> data bundle, all in dist/
npm run build        # build:wasm + TypeScript (via unbuild)

scripts/build-wasm.mjs is cross-platform (Windows/Linux/macOS). The wasm32-wasi target name from older docs is now wasm32-wasip1 in Rust; this project does not use it (see "No WASI" above).

Auto-download: On first build (or when the upstream db-latest release updates), the script downloads ~100 MB compressed database, verifies SHA256, decompresses to .explainshell-cache/, and exports the MessagePack bundle. Subsequent builds use the cache. Set GITHUB_TOKEN env var for higher API rate limits (60/hr unauthenticated).

Development

npm install          # install JS dependencies
npm run build        # builds WASM + TypeScript (via unbuild)
npm test             # Node suites: unit + integration over HTTP
npm run test:browser # real headless Chromium via @vitest/browser + Playwright
npm run lint         # Biome check
npm run lint:fix     # Biome check --write

Git hooks (Husky + lint-staged) run Biome on staged JS/TS files at commit and the test suite on push.

Test matrix

| Suite | Environment | What it covers | | ----------------------- | ---------------------- | ------------------------------------------------------- | | api.test.ts | Node | Type shapes, response envelopes | | matcher.test.ts | Node + built .wasm | explain() groups, unknown-program errors, empty input | | browser-serve.test.ts | Node + local HTTP | BrowserExplainshell fetching glue/wasm/data over HTTP | | browser.test.ts | Real headless Chromium | Full browser path: fetch, instantiate, explain |

Integration suites skip automatically when dist/ is not built.

Project structure

explainshell-wasm/
├── Cargo.toml                  # Rust workspace
├── crates/
│   ├── core/                   # domain types (CliOption, ParsedManpage, ...)
│   ├── data/                   # msgpack bundle loading + lookup
│   ├── parse/                  # shell parser (bashlex port)
│   ├── match/                  # matcher algorithm
│   └── wasm/                   # wasm-bindgen exports (Explainshell)
├── src/
│   ├── index.ts                # public entry point
│   ├── api.ts                  # createExplainshell / explain / resetExplainshell
│   ├── types.ts                # ExplainOptions, ExplainResult, ...
│   ├── runtime/
│   │   ├── node.ts             # Node loader (reads dist/ from disk)
│   │   ├── browser.ts          # browser loader (fetch)
│   │   └── utils.ts            # glue shape guards, response validation
│   └── __tests__/              # Vitest suites
├── test/
│   ├── fixtures/               # test.db + test.data.msgpack (CI fixture)
│   └── serve-dist.ts           # globalSetup: serves dist/ to browser tests
├── scripts/                    # build-wasm.mjs, export_wasm_data.py, ...
├── build.config.ts             # unbuild configuration
├── vitest.config.ts            # Node suites
└── vitest.browser.config.ts    # Chromium suite

dist/ (compiled JS, .wasm, glue, data bundle, generated .d.ts) and target/ are gitignored build outputs.

Known limitations

  • The default bundle is the 4-manpage CI fixture; real coverage needs a full explainshell.db (see Data notice).
  • The data bundle for a full distro is ~20-30 MB; reuse the instance returned by createExplainshell.
  • The matcher currently resolves commands to manpages; full token-level option matching lands with the bashlex port.
  • Browser testing covers headless Chromium. Other engines should work (the module only needs post-MVP features all modern browsers ship), but they are not in the matrix.