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

bancada

v0.2.0

Published

A local-first study workbench: markdown lessons, your notes, and plugins for the subject you are learning

Readme

bancada

A local-first study workbench. Markdown lessons on disk, your notes beside them, and plugins for whatever you are actually learning.

It is meant for the kind of study that has things in it — electronics, 3D printing, woodworking — where a lesson is not only text but also a parts list, a diagram, a model. The shell knows nothing about any of that. Plugins do.

Status

v0, in development. The contract, the loader, the theme catalogue and the viewer are all here. Not published to npm yet.

Why

A repository of markdown files is already a good place to study: it is versioned, greppable, and yours. What it lacks is a way to read it that knows your subject — to show a parts list next to the lesson that uses it, to draw the circuit the text describes, to say "you cannot build this one, you are two resistors short".

Every tool that does that is welded to one subject. bancada is the half that is not.

Requirements

bun 1.3 or newer. The package ships TypeScript with no build step and uses Bun.file/Bun.write to read the repository, so it does not run on node.

Install

bun add bancada
bun add @bancada/inventory @bancada/electronics   # the native plugins

Running it

import { createViewer, readConfig } from "bancada";

const viewer = await createViewer(await readConfig());

Bun.serve({
  port: 4321,
  fetch: (req, server) => viewer.handleRequest(req, { ip: server.requestIP(req)?.address }),
});

createViewer resolves the config, loads the plugins and returns a request handler. It takes the repository root as a second argument, defaulting to the working directory — a package cannot know where it was installed, and two repositories in one process must not share state.

The handler serves the pages and one write endpoint, POST /api/notes/:id, which refuses anything that does not come from loopback. Binding wide is how the browser reaches it across a VM boundary; refusing the write is why that is safe.

What the shell does

  • renders markdown lessons in a reading order you declare
  • keeps your notes and progress outside the lesson folders, so the content can be shared as a template without your progress riding along
  • a section index, syntax highlighting, copy buttons
  • 18 themes and a font choice, because study reading is long

What a plugin does

A plugin joins the page build at any of ten stages:

| stage | what it contributes | |---|---| | configure | validates its own config, fails early | | onLesson | enriches the lesson before anyone draws it | | cards | cards alongside the lesson | | transformBody | rewrites the rendered HTML | | styles / scripts / assets | what its UI needs | | routes | pages of its own | | menuItems | entries in the sidebar | | validate | findings for the repository check |

A plugin script exports its factory as default, or as a named export called plugin. The loader does not guess — a module with several exported functions and neither name is refused, with a message listing what it did find.

The order is the contract. A plugin declared after another sees what the previous one produced — which is how one plugin uses another's work without importing it. The electronics plugin reads the parts the inventory plugin resolved; neither knows the other exists.

Two natures of stage, and they are not interchangeable:

  • collecting (cards, routes, assets…) gathers from everyone
  • piping (onLesson, transformBody) hands the value down the line

collect() is always async, even for stages that happen to be synchronous today: a caller forced to know which stages read files gets it wrong the first time one starts to.

Native plugins

Config

One bancada.config.json at the repository root. It holds what would otherwise be hardcoded — the vocabulary, the note sections, the theme, the UI strings — and declares the plugins with each one's settings.

{
  "title": "Electronics",
  "lang": "en",
  "content": { "lessons": "lessons", "reference": "reference", "mine": "mine" },
  "notes": { "sections": ["What went wrong", "What I did not understand"] },
  "theme": { "default": "paper", "font": "serif" },
  "plugins": [
    { "name": "inventory", "script": "@bancada/inventory", "config": { "file": "inventory.yml" } }
  ]
}

Labels default to English. A repository in another language overrides only what it needs — a public shell cannot hardcode one language, and a half-translated page is worse than an English one. lang goes into <html lang>, which is what a screen reader reads pronunciation from.

Prose labels take {placeholder} markers, filled by fill(). A marker nobody supplies is left on screen rather than becoming undefined: seeing {tab} tells you which label is wrong.

The CSS contract

A plugin returns HTML that lands inside the shell's page, so the class names and the colour variables are as much of an API as the TypeScript is. Renaming one is a breaking change.

The shell styles these; use them and your cards look like the built-in ones:

| class | what it is | |---|---| | card | a titled block beside the lesson — what cards() should return | | part | a label/value row inside a card | | badge | a small pill, for counts and states | | actions | a row of buttons | | copy-file + source | a copy button and the <pre hidden> holding its text |

Colours come from the active theme. Never hardcode one — the reader picked the theme, and a fixed colour ignores them:

--bg --text --muted --surface --border --accent --code-bg --canvas-bg --canvas-grid --code-comment --code-keyword --code-string --code-number --code-function --code-meta --code-type

License

MIT.