Anonymous runtime analytics for npm packages.
Real usage β not download noise.
npm download counts are useless for understanding if anyone actually uses your package.
They count CI installs. Mirror caches. Bots. npm install in pipelines that never execute your code. A package with 100k weekly downloads might have zero real users.
pinglet tells you the truth: who's running your package, how often, and which parts.
| npm downloads | pinglet | |
|---|---|---|
| Someone actually ran my code? | β | β |
| How many active anonymous users? | β | β |
| Which commands/features? | β | β |
| Which versions still active? | β | β |
| CI or real person? | β | β |
| macOS, Linux, Windows? | β | β |
Railway (easiest): push this repo β create project β add volume β done.
Full guide: docs/deploy-railway.md.
Docker: docker build -t pinglet . && docker run -p 3456:3456 -e PINGLET_ADMIN_PASSWORD=... pinglet
Self-host: npx -p @black-knight.dev/pinglet pinglet-server
# Install the CLI (globally or via npx)
npm install -g @black-knight.dev/pinglet
# or: npx -p @black-knight.dev/pinglet pinglet login ...
pinglet login --url https://your-server.example.com --user adminStores a 30-day token β not your password.
npm install @black-knight.dev/pingletimport { Pinglet } from '@black-knight.dev/pinglet';
const analytics = new Pinglet({
packageName: 'my-package',
packageVersion: '1.0.0',
endpoint: 'https://your-server.example.com/ping',
});
await analytics.track('run');
await analytics.track('command:build');That's it. Your package now sends anonymous runtime pings.
pinglet # quick overview
pinglet my-package # detailed stats{
"pkg": "my-package",
"totalPings": 1420,
"uniqueUsers": 312,
"events": { "run": 980, "command:build": 310 },
"versions": { "1.4.0": 900, "1.3.0": 520 },
"platforms": { "darwin": 800, "linux": 500, "win32": 120 },
"ci": { "true": 40, "false": 1380 },
"days": { "2026-06-03": 1420 }
}# Install: npm install -g @black-knight.dev/pinglet
# Or via npx: npx -p @black-knight.dev/pinglet pinglet <command>
pinglet # status overview
pinglet <pkg> # stats for package
pinglet ls # list all tracked packages
pinglet show <pkg> # same as pinglet <pkg>
pinglet snippet <pkg> # print copy-paste SDK code
pinglet health # server health check
pinglet login --url <url> # login once
pinglet logout # remove local login| Collected | Not collected |
|---|---|
| Random hashed client id | Hardware id, hostname, username |
| Event name | File paths, source code, logs |
| Package name + version | Environment variables, secrets |
| Node.js version | User-generated content |
| Platform (darwin/linux/win32) | IP address |
| CI flag | Client timezone |
Full privacy model: docs/security.md.
pinglet uses the industry-standard opt-out model (like Next.js, VS Code, Homebrew):
- Tracking is ON by default at level 1 (basic: all event names, no properties)
- No prompts during
npm installβ no postinstall script - Documented in every README β open source transparency
- Inspect before sending:
PINGLET_DEBUG=1shows the JSON payload without transmitting - Opt out anytime:
PINGLET_OPT_OUT=1,DO_NOT_TRACK=1,--no-telemetry
For more detail: docs/telemetry.md.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/ping |
public | Receive a runtime event |
POST |
/auth/login |
Basic Auth | Create 30-day admin token |
GET |
/auth/check |
Bearer token | Verify saved login |
GET |
/packages |
admin | List tracked packages |
GET |
/stats?pkg=<name> |
admin | Aggregated analytics |
GET |
/health |
public | Health check |
| Platform | Guide |
|---|---|
| Railway | docs/deploy-railway.md β step by step |
| Docker | docker build -t pinglet . β single command |
| Fly.io | fly launch --dockerfile Dockerfile |
| Self-host | npx -p @black-knight.dev/pinglet pinglet-server |
| Overview | docs/deployment.md β all options |
All deployments need one env var: PINGLET_ADMIN_PASSWORD.
| Option | Required | Description |
|---|---|---|
packageName |
β | Your package name |
packageVersion |
β | Current version |
endpoint |
β | URL receiving POST /ping |
salt |
β | Stable salt for anonymous client id |
silent |
β | Suppress console output |
timeoutMs |
β | Network timeout (default 1500ms) |
ingestToken |
β | Write token for private endpoints |
meta |
β | Non-PII properties on every event |
await analytics.init() // prepare client
await analytics.track('event') // send event β never throws
analytics.optOut() // persist disable
analytics.optIn() // re-enable
analytics.isOptedOut // current state| Doc | Topic |
|---|---|
deployment.md |
Deploy (Railway, Docker, Fly) |
deploy-railway.md |
Railway guide |
security.md |
Privacy, GDPR, hardening |
maintainer-guide.md |
Add pinglet to your package |
agent-quickstart.md |
For AI agents |
market-research.md |
Why pinglet |
examples/basic-cli.mjs |
Working example |
- Add a visible
Telemetrysection to your README - Events go low-cardinality:
command:build, not raw input - Never track: paths, source code, logs, stack traces, secrets
- Support
DO_NOT_TRACK=1+PINGLET_OPT_OUT+--no-telemetry - Offer
PINGLET_DEBUG=1so users can inspect the payload before trusting - Telemetry failures are always silent and non-blocking
MIT
