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

portfolio-presence

v0.3.0

Published

Privacy-first presence snapshots for portfolio sites.

Readme

portfolio-presence

Privacy-first presence snapshots for portfolio sites.

Full documentation: https://deveshsangwan.github.io/portfolio-presence/

portfolio-presence turns a few low-maintenance activity sources into a small public JSON snapshot you can render however you want. It is designed for portfolio cards like:

  • Building: latest selected GitHub repo
  • Playing: latest game recorded by an iOS Shortcut
  • Listening to: Last.fm recent track

It is not a realtime presence system. The intended shape is "last known public activity, cached and safe to display."

Install

pnpm add portfolio-presence

Package Shape

The package is hybrid by design:

  • portfolio-presence: framework-neutral core, sources, store interface
  • portfolio-presence/next: Next.js App Router compatible route helpers
  • portfolio-presence/react: optional headless client hook

There are no runtime dependencies. React is an optional peer dependency used only by the /react export.

Configure Presence

Create a shared server-side module:

// lib/presence.ts
import {
  definePresence,
  githubSource,
  lastFmSource,
  memoryStore,
  playedEventSource
} from "portfolio-presence";

const store = memoryStore();

export const presence = definePresence({
  cache: {
    store,
    ttlSeconds: 60
  },
  sources: {
    building: githubSource({
      username: "deveshsangwan",
      token: process.env.GITHUB_TOKEN,
      mode: "public",
      excludeRepos: ["old-demo", "test-repo"],
      includeForks: false,
      includeArchived: false
    }),
    playing: playedEventSource({
      store
    }),
    listening: lastFmSource({
      username: process.env.LASTFM_USERNAME!,
      apiKey: process.env.LASTFM_API_KEY!,
      blockedArtists: [],
      blockedTracks: []
    })
  },
  fallbacks: {
    building: {
      title: "Investment Sync",
      href: "https://github.com/deveshsangwan/investment-sync"
    },
    playing: {
      title: "MCOC",
      platform: "ios"
    }
  }
});

cache.ttlSeconds controls snapshot freshness and is passed to the configured store as its physical TTL. cache.lastGoodTtlSeconds is optional: omit it to keep the last-good fallback key indefinitely, or set it to expire that key too. For the played-event key, pass ttlSeconds to playedEventSource. A zero TTL means that key is not retained. TTLs must be finite, non-negative numbers.

When using Redis or another persistent store, its set implementation must honor PresenceStore's ttlSeconds option—for Redis, that means using the client's expiry option (for example, EX).

memoryStore() is useful for local development, but it is not persistent across serverless cold starts. For production, implement PresenceStore with Redis, Upstash, Vercel KV, Postgres, or the storage your portfolio already uses.

Next.js Routes

// app/api/presence/route.ts
import { createPresenceGetHandler } from "portfolio-presence/next";
import { presence } from "@/lib/presence";

export const GET = createPresenceGetHandler(presence);
// app/api/presence/played/route.ts
import { createPlayedIngestHandler } from "portfolio-presence/next";
import { presence } from "@/lib/presence";

export const POST = createPlayedIngestHandler(presence, {
  secret: process.env.PRESENCE_INGEST_SECRET!
});

iOS Shortcut Payload

Send a POST request when you open a game:

POST /api/presence/played
Authorization: Bearer <PRESENCE_INGEST_SECRET>
Content-Type: application/json
{
  "title": "MCOC",
  "platform": "ios",
  "url": "https://apps.apple.com/app/id1095691691",
  "occurredAt": "2026-06-13T10:30:00.000Z"
}

Rendering

Server-side rendering is the simplest option:

const snapshot = await presence.getSnapshot();

Client-side rendering is available through the headless hook:

"use client";

import { usePresence } from "portfolio-presence/react";

export function PresencePills() {
  const { snapshot } = usePresence("/api/presence");

  return snapshot?.cards.map((card) => (
    <a key={card.kind} href={card.href}>
      {card.label}: {card.title}
    </a>
  ));
}

Snapshot Shape

{
  generatedAt: string,
  cards: [
    {
      kind: "building",
      label: "Building",
      title: "Investment Sync",
      href: "https://github.com/...",
      source: "github",
      updatedAt: "2026-06-13T10:00:00.000Z",
      stale: false
    }
  ],
  sources: {
    building: { status: "fresh", source: "github" },
    playing: { status: "fallback", source: "manual" },
    listening: { status: "fresh", source: "lastfm" }
  }
}

Privacy Defaults

  • GitHub can use public owner repos or an explicit repo allowlist.
  • Private GitHub repos are skipped unless allowPrivate: true is set in allowlist mode.
  • Private repo names are not exposed by default.
  • Last.fm supports blocked artists and blocked tracks.
  • Played ingestion requires a secret in the Next.js helper.
  • Public snapshots never include raw provider payloads.
  • Provider failures use stale last-good data or fallbacks instead of breaking the page.

V1 Scope

Included:

  • GitHub source for recently building
  • Last.fm source for recently listening
  • Recordable played-event source for iOS Shortcuts
  • Framework-neutral cache/store model
  • Next.js App Router route helpers
  • Optional React hook

Skipped for v1:

  • WakaTime
  • Steam, Xbox, PlayStation, Discord, Spotify, Apple Music
  • OAuth flows
  • Realtime updates
  • Dashboard/admin UI
  • Styled React components
  • Multi-user SaaS behavior