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

@profullstack/openprofile

v0.2.0

Published

Read, write, merge and de-duplicate OpenProfile.md documents (logicsrc.com/openprofile), including the Broadcast (OpenBroadcast) and Guest (OpenGuest) sections. One parser for every house app that serves a profile.

Readme

@profullstack/openprofile

One reader and writer for OpenProfile.md, the Markdown profile file for people and agents, with its Broadcast and Guest sections. Every house app that serves a profile reads, renders, corrects and de-duplicates it through this package, so a profile means the same thing on p0dcasters, OutreachGraph, rssamplifier, nichedb and myna.

npm i @profullstack/openprofile

Read

import { parseOpenProfile, accounts, topics, kindOf, emoji, pronouns, web, broadcasts, guest } from "@profullstack/openprofile";

const doc = parseOpenProfile(markdown);
doc.name;                 // the `#` heading, or null
doc.identity;             // [{ key: "Kind", value: "person" }, ...] as written
doc.headline;             // the one prose line after the identity block
doc.sections;             // [{ title: "Find me", name: "accounts", body: "- ..." }]
accounts(doc);            // [{ url, label }] the URL is the identity
topics(doc);              // ["computing history", "mathematics"]
kindOf(doc);              // "person" | "agent" | "organization" | null
emoji(doc);               // "🔭": the person's mark, first grapheme; emoji(doc, resolve) for :shortcodes:
pronouns(doc);            // "she/her" as written, or null (never inferred)
web(doc);                 // the home page, written as Web, Website, Homepage or Site
broadcasts(doc);          // one keyed object per show, `###` groups split apart
guest(doc);               // the Guest keys, or null

Emoji, Pronouns and Web are default fields since OpenProfile 0.4. identityValue, identityMap, overlays, merges and de-duplication treat Website, Homepage and Site as Web (canonicalKey, IDENTITY_ALIASES), and makeOpenProfile writes them as Web.

Nothing is required and nothing is dropped. An unknown identity key, an unknown section and a second prose paragraph all survive a parse and a render. Section titles are kept verbatim and normalised separately (Find me is accounts), as rule 4 says.

Write

import { makeOpenProfile, keyedSection, listSection, renderOpenProfile } from "@profullstack/openprofile";

const md = renderOpenProfile(makeOpenProfile({
  name: "Ada Lovelace",
  identity: { Kind: "person", Web: "https://ada.example", Avatar: image },
  headline: "Host of The Analytical Engine.",
  sections: [
    listSection("Accounts", ["https://bsky.app/profile/ada.example"]),
    listSection("Topics", categories),
    keyedSection("Broadcast", { Show: title, Kind: "podcast", Feed: feedUrl, Listen: page, Topics: categories.join(", ") }),
  ],
}));

Absence is unstated: a null or empty value is not written, an empty section is not written. Known sections come out in the spec's order.

Let the person correct it

A generated profile is the app's claim. The person it is about corrects it with an overlay, and the overlay is the same shape over the API, the CLI, the MCP tool and the web form:

import { applyOverrides, overridesFromDocument, mergeOverrides } from "@profullstack/openprofile";

const shown = applyOverrides(generated, overrides);
// overrides = { name?, headline?, prose?, identity?: { Email: null, Location: "London" },
//               sections?: { guest: "- **Available**: yes", colophon: "none" } }

The owner's identity keys, headline and sections win; a section the owner did not touch is still generated; a section written as the single word none is removed. A whole edited file becomes an overlay with overridesFromDocument(markdown, generated), so PUT of Markdown and PUT of JSON store the same thing. mergeOverrides(base, patch) is the partial update.

De-duplicate and merge

import { identityKeys, samePerson, mergeProfiles, normaliseUrl } from "@profullstack/openprofile";

identityKeys(doc);        // ["web:ada.example", "account:github.com/ada", "email:[email protected]"]
samePerson(a, b);         // true when they share one key; a shared name never counts
mergeProfiles([a, b]);    // first document's identity wins, Accounts and Topics unioned,
                          // Broadcast sections kept apart as `### <show>` groups

normaliseUrl lowercases the host, drops www., the scheme, tracking keys, the fragment and the trailing slash, so HTTPS://www.GitHub.com/Ada/?utm_source=x and github.com/ada are one key. It never throws on a malformed percent-escape: a stray % is literal (so /%draft% and /%25draft%25 are one key), and a path that will not decode as UTF-8 (%FF) keeps its escapes.

Constants

MEDIA_TYPE (text/markdown; charset=utf-8), LINK_REL (openprofile), WELL_KNOWN_PATH (/.well-known/openprofile.md), EDIT_SCOPE (openprofile:edit, the OpenAccess scope an app asks for to edit a profile on the person's behalf).

License

MIT.