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

skewffle

v0.1.0

Published

Weighted auto-queue service for MPD: rating-skewed shuffle with album and rediscovery modes, play history, Last.fm loved-tracks sync, and a web UI.

Readme

Skewffle

Skewffle (skew + shuffle) is a weighted auto-queue service for MPD (Music Player Daemon). Like ashuffle, it watches MPD and keeps the play queue topped up with random tracks — but instead of a uniform shuffle, every track, album, and artist can carry a rating that skews its selection probability. Favorites appear more often, deprioritized music appears rarely but is never fully gone, and smart modes (whole-album adds, rediscovery of long-unplayed tracks) keep the queue both familiar and fresh.

Features

  • Keeps MPD's queue fed using ashuffle's proven event-driven core: blocks on MPD idle, reacts to queue/player/database events, maintains a configurable queue buffer, and reconnects forever after MPD outages.
  • Ratings with fallback — integer 0–10 on tracks, albums, and artists; the effective rating resolves track → album → artist → default (5). Selection weight is linear in the effective rating: a 10-rated track plays twice as often as a neutral one.
  • Rating 0 and hard bans — rating 0 removes a track from the shuffle but keeps it inside whole-album adds; a banned track never plays, not even as part of an album.
  • Exclude rules with ashuffle semantics (case-insensitive substring, AND within a rule, OR across rules), plus one-time import of an existing ashuffle excludes.yaml.
  • Album mode — occasionally (or exclusively) add a whole album, selected by album rating, with configurable probability, time-of-day/day-of-week windows, and an anti-repeat cooldown.
  • Rediscovery mode — a periodic pull toward tracks that have not played in months (or ever).
  • Play history — counts a play when ≥ 50 % (configurable) of the duration was actually listened, derived from MPD player events. Powers rediscovery and the stats page.
  • Last.fm loved tracks — periodic sync assigns loved tracks a configurable high rating (manual ratings always win; un-loved tracks revert).
  • Tag-based identity — ratings, bans, and history are keyed by normalized tags, not file paths, so everything survives renames, moves, and format changes. Fully exportable/importable.
  • Persistent shuffle state — the anti-repeat window and pool ordering survive restarts.
  • Web UI — status dashboard with live updates, library browser with inline rating/ban editing, rules management, history and stats, settings applied without restart.

Quick start

Skewffle needs Node.js ≥ 24 and a reachable MPD server.

npx skewffle

Then open http://127.0.0.1:4816. MPD location comes from the standard MPD_HOST/MPD_PORT environment variables or flags:

npx skewffle --mpd-host 192.168.1.10 --mpd-port 6600
npx skewffle --mpd-host /run/mpd/socket
npx skewffle --mpd-host [email protected]

Docker

docker run -d \
	-e MPD_HOST=mpd.local \
	-v skewffle-data:/data \
	-p 4816:4816 \
	ghcr.io/avol-v/skewffle

The image is published to both GitHub Container Registry (ghcr.io/avol-v/skewffle) and Docker Hub (docker.io/avolv/skewffle).

See docker-compose.example.yml for pairing with an MPD container.

Configuration

Bootstrap options (flags / environment)

These are needed before the database opens; changing them requires a restart.

| Flag | Environment variable | Default | |---|---|---| | --data-dir | SKEWFFLE_DATA_DIR | $XDG_DATA_HOME/skewffle~/.local/share/skewffle | | --http-host | SKEWFFLE_HTTP_HOST | 127.0.0.1 (Docker image: 0.0.0.0) | | --http-port | SKEWFFLE_HTTP_PORT | 4816 | | --mpd-host | MPD_HOST | localhost (supports password@host and socket paths) | | --mpd-port | MPD_PORT | 6600 |

Precedence: flag > environment variable > default.

Runtime settings (web UI, applied live)

Everything else lives in SQLite and is edited on the Settings page — no restart needed: queue buffer, shuffle window size, play-on-startup, suspend timeout, default rating, album mode (probability, minimum tracks, cooldown, time windows), rediscovery (probability, staleness threshold), play-count threshold, Last.fm credentials and sync interval. Settings and library data (ratings/bans/history) can be exported and imported as JSON/YAML (data also as CSV) from the same page.

The defaults mirror a typical ashuffle setup: queue-buffer 1, window-size 25, play-on-startup off.

How selection works

For every queue slot Skewffle decides which picker runs:

  1. Album mode (if enabled and inside an allowed time window) — with the configured probability, one album is drawn weighted by its effective rating and added whole, in album order. Banned and rule-excluded tracks are punched out; rating-0 tracks stay.
  2. Rediscovery (with its probability) — one long-unplayed track, weighted by rating.
  3. Weighted shuffle — ashuffle's window+pool algorithm with rating-weighted sampling: after a track plays it cannot repeat for at least window-size further picks, and within the pool, higher-rated tracks are drawn more often.

Tracks queued by any picker share one cooldown, so an album add will not be followed by the same songs from the shuffle. Full details in docs/selection-engine.md.

| Mechanism | In weighted shuffle? | In album adds? | In rediscovery? | |---|---|---|---| | Normal track (rating ≥ 1 or unrated) | yes, weighted | yes | yes, if stale | | Effective rating 0 | no | yes | no | | Banned track | no | no | no | | Matches an exclude rule | no | no | no | | Not in the MPD library anymore | no | no | no |

Migrating from ashuffle

  • The core behavior (queue buffer, window cooldown, suspend timeout, single-mode handling) is a faithful port; the defaults match ashuffle --queue-buffer 1 --tweak window-size=25 --tweak play-on-startup=no.
  • Import your excludes.yaml on the Rules page — the semantics are identical.

HTTP API

The web UI is backed by a JSON API under /api (status, SSE events, queue, player controls, library, rules, settings, history, stats, import/export, Last.fm sync). There is no authentication — Skewffle is a trusted local/LAN service and binds to 127.0.0.1 by default. Do not expose it to untrusted networks.

Development

npm install
node --run dev        # server from TypeScript sources (native type stripping)
node --run dev:web    # Vite dev server for the UI (proxies /api)
node --run typecheck
node --run test       # Node.js native test runner
node --run build      # dist/server (tsc) + dist/web (vite)

Architecture notes live in docs/. The stack is deliberately lean: Hono + @hono/node-server + yaml at runtime, node:sqlite for storage, a hand-rolled MPD protocol client, and SolidJS + Vite for the UI (dev-only dependencies).

License

MIT