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

lmfpd-sdk

v0.2.0

Published

Language model fingerprint detection: score saved replies or run a live detection round

Readme

lmfpd-sdk

TypeScript SDK for lmfpd, the language-model fingerprint detector. It scores saved replies to the number challenge, generates challenges, and runs one live detection round against an OpenAI Responses, Chat Completions, or Anthropic Messages API. Rust, Go, and Python SDKs with the same results live beside this package.

The results are a closed-set ranking within the reference bank. They are not identity confirmation.

Documentation: https://lm.ikale.io/docs/en/sdk

npm install lmfpd-sdk   # Node 22+ or Bun 1.4.2+
import { loadDetector } from 'lmfpd-sdk'

const detector = await loadDetector()
const analysis = detector.analyze([
  { text: '17, 203, 88, …', expected_count: 300 },
  { text: '…', expected_count: 311 },
  { text: '…', expected_count: 296 },
])
console.log(analysis.decision, analysis.prediction, analysis.probability)

const result = await detector.detect({ baseUrl: 'https://api.openai.com/v1', apiKey: process.env.API_KEY!, model: 'gpt-5' })
console.log(result.analysis?.prediction ?? result.error)

Reference data

The package contains no reference data. loadDetector() downloads the detector (about 3.6 MB compressed) from the first working source in DEFAULT_DATA_URLS:

  1. https://lm.ikale.io/data/
  2. https://cdn.jsdmirror.com/npm/lmfpd-data@latest/, a mirror in mainland China

The data is cached in one file, <OS cache directory>/lmfpd/data.bin by default (cache sets another). A load with a cache returns at once; when the cache was last checked ttl seconds ago or earlier (default 86400; 0 checks on every load), it also refreshes the cache in the background for the next load. Without a cache the data is downloaded before loadDetector returns. A single source that is not an http(s) URL is a local directory: loadDetector({ source: './data' }) reads unified_bank.json and shared_detector.bin from it without network access.

Bun reads HTTPS_PROXY for every request. Node reads it only with NODE_USE_ENV_PROXY=1.

API

| Export | Description | |---|---| | loadDetector({ source?, cache?, ttl? }) | Loads the reference data from URLs through the cache file, or from a local directory. | | detector.builtAt | Build time of the reference bank. | | detector.analyze(outputs, { strict? }) | Scores one to three replies. Three valid replies give a calibrated ranking; fewer give a partial ranking unless strict. | | detector.detect(config, { signal?, onSample? }) | Generates config.count challenges, sends them to the model API, and scores the replies. | | generateChallenges(count = 3) | One to three random challenges, each asking for a distinct count from 292 to 332. | | parseNumbers(text) | The numbers 1 to 355 of the longest run in a reply. | | buildRequest(config, prompt) | The URL, headers, and body detect sends, or { error }. | | requestSample(config, challenge) | Sends one challenge and returns the checked reply. | | DEFAULT_DATA_URLS | The default data sources. |

DetectConfig takes baseUrl, apiKey, and model, plus api (responses, chat_completions, or messages), effort, serviceTier, stream (default true), strict, count (default 3), parallel (default 3), and timeout in seconds (default 120, 0 disables).

onSample(index, sample) receives a copy of a sample each time it changes: queued for every sample first, then waiting once its request is sent, streaming with a growing count as SSE text arrives, and finally complete, truncated, failed, or cancelled. index is the sample's position in the result. Calls never overlap, and a thrown error is reported as an uncaught exception without affecting the detection. sample.first_byte_ms is the time to the first SSE body data, or to the whole JSON response.

const result = await detector.detect(config, {
  onSample: (index, sample) => console.log(index, sample.state, `${sample.count}/${sample.expected_count}`),
})

Analysis.decision is scored, partial, unscorable, or not_model. results lists the candidates by ranking score; probability is the closed-set probability of the top candidate and is null without three valid replies.