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

@kotuke/bitizen

v2.0.0

Published

Deterministic, zero-dependency pixel avatars: the whole figure is personal, in two styles

Readme

Bitizen

test npm license: MIT

Deterministic pixel avatar people. The same userId with the same AVATAR_SECRET always yields the same citizen. Generation is entirely local: no network calls, no database, no runtime dependencies.

Two styles ship in one package, picked with the style option:

Bitizen: the plain style on top, the rich style below

Live gallery → — 50 avatars per style.

The top two rows are plain, the bottom two are rich.

| | plain | rich | | --- | --- | --- | | Background | plain black | plain black | | Color | a single paint for the whole figure | 5 color schemes plus second-color trim | | Body | solid | cutouts: visor, mouth, chest slot | | Symmetry | always mirrored | about half of the figures are asymmetric | | Eyes | always white | always white | | Variants | ~4·10⁷ | ~10⁹ |

What both styles share: the figure is one connected shape built from square modules, the eyes are always white and identical, and the seed comes from HMAC-SHA-256, so the raw userId never reaches the image.

What varies

| Part | plain | rich | | --- | --- | --- | | Head | width 5…9, height 3…5, 4 shapes | same plus a crest, 5 shapes | | Ears | none or both sides | none, both, left only, right only | | Antenna | none, rod, pair, rod with a crossbar | same plus a single side rod, length 1…3 | | Neck | height 0…2, width 1 or 3 | same | | Torso | width 5…9, height 3…5, 3 shapes, shoulders | same | | Arms | 4 styles, length 2…4 | same; the sides differ in 20% of cases | | Legs | height 2…4, width 1…2, feet | same plus feet on both sides | | Cutouts | — | visor, 3 mouth variants, chest slot | | Trim | — | belt, chest panel, pauldrons | | Color | 12 accents | 12 accents × 5 schemes |

In rich, cutouts are applied one at a time and rolled back if they would split the figure, so a bot always stays a single connected shape.

rich color schemes

| Scheme | Result | | --- | --- | | 0 | mono: the whole figure in the accent | | 1 | head, ears and antenna lighter than the body | | 2 | arms and legs darker than the body | | 3 | torso and neck neutral grey, the rest in the accent | | 4 | only the head in the accent, the rest neutral grey |

Install

npm install @kotuke/bitizen

Two styles ship in one package; pick one with the style option. Node.js 20 or newer; the package has no runtime dependencies. A browser-only entry point is available as @kotuke/bitizen/browser — it renders SVG without node:crypto or zlib.

Quick start

Node.js 20 or newer is required.

AVATAR_SECRET='replace-with-a-long-random-secret' npm start
GET http://127.0.0.1:3000/avatars/v1/user-123.png
GET http://127.0.0.1:3000/avatars/v1/user-123.png?style=rich
GET http://127.0.0.1:3000/avatars/v1/user-123.svg?size=512&style=rich
GET http://127.0.0.1:3000/health

Sizes are 32…2048, default 256. Style is plain (default) or rich; an unknown style returns 400. The ETag includes the style, so the two styles never share a cache entry.

Why a secret

The avatar is a function of userId. Without a secret that function is fully predictable: anyone who knows the algorithm can render an avatar for a guessed identifier and compare it with what your site serves, which turns the avatar into an oracle for "does this email have an account here?". Keying the hash with AVATAR_SECRET keeps the image deterministic for you and unreproducible for everyone else, and lets you rotate the whole avatar set at once if you ever need to. Keep the secret on the backend: never ship it to the client, never put it in a URL.

Use as a module

import { generateAvatarPng, generateAvatarSvg } from "@kotuke/bitizen";

const options = { secret: process.env.AVATAR_SECRET, size: 256 };

const plain = generateAvatarPng("user-123", options);
const rich = generateAvatarPng("user-123", { ...options, style: "rich" });
const svg = generateAvatarSvg("user-123", { ...options, style: "rich" });

When both formats are needed, compute the descriptor once:

import {
  createAvatarDescriptor,
  renderAvatarPng,
  renderAvatarSvg,
} from "@kotuke/bitizen";

const bot = createAvatarDescriptor("user-123", {
  secret: process.env.AVATAR_SECRET,
  style: "rich",
});

const svg = renderAvatarSvg(bot, { size: 256 });
const png = renderAvatarPng(bot, { size: 256 });

A descriptor always carries its own style; the renderer is chosen from it, and a descriptor from the wrong style is rejected before any XML is written. The two shapes differ:

// style: "plain"
{ version, fingerprint, accent, pixels: [[column, row], …], eyes, rows, style }

// style: "rich"
{ version, fingerprint, accent, scheme, layers: [{ color, pixels }, …], eyes, rows, columns, style }

CLI

AVATAR_SECRET='replace-with-a-long-random-secret' \
  node src/cli.mjs --id user-123 --out bot.png --format png --size 512 --style rich

Checks and examples

npm test        # 40 tests: both styles, the facade and HTTP
npm run preview # rebuild preview.png
npm run docs    # rebuild the GitHub Pages gallery in docs/
npm run sample  # 12 examples per style into examples/generated/
npm run benchmark
npm run collision

Measured on the machine this package was built on:

| Style | SVG 256px | PNG 256px | Repeats per 100,000 | | --- | --- | --- | --- | | plain | 0.20 ms | 0.84 ms | 26 | | rich | 0.29 ms | 1.01 ms | 0 |

Stability contract

An avatar is defined by four inputs:

AVATAR_VERSION + AVATAR_SECRET + style + userId

Do not change AVATAR_SECRET if existing avatars must stay the same. Switching the style of an already registered user also changes their picture. When the algorithm changes, bump AVATAR_VERSION and serve a new URL such as /avatars/v2/... — that invalidates long-lived CDN caches correctly.

Prefer an existing public UUID or a separate opaque avatarKey in the URL rather than an email address or a phone number.

Uniqueness

A visual collision is theoretically possible, as with any finite identicon. If your product needs strict uniqueness, store the visual signature at registration and, on a clash, append a stable avatarVariant to the input identifier, for example ${user.id}:${user.avatarVariant}.

Origin

Grown out of bitling, where everyone shared the same white robot and only the sigil above it was personal. Bitizen drops the sigil and makes the figure itself personal.

Using with an AI assistant

llms.txt is a compact, machine-readable summary of the whole API — options, error behaviour, descriptor shape and the determinism contract. Point an assistant at https://kotuke.github.io/bitizen/llms.txt (or paste the file) and it has everything needed to wire the library up correctly without reading the source.

Contributing

Pull requests are welcome. Before sending one:

npm ci
npm run lint
npm test

Formatting follows .editorconfig (two spaces, LF, UTF-8), and the code style is whatever the surrounding file already does. If a change affects the generated figures, say so explicitly in the PR — avatars are a stability contract, and the same userId is expected to keep its picture.

License

MIT — see LICENSE.