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

@hanzo/sentinel

v0.1.1

Published

Hanzo Sentinel — the client for the error plane's read face. Lists and resolves grouped issues, reads captured errors, manages projects and their DSNs, and queries logs, stats, traces and aggregates over /v1/sentinel. The write side is @hanzo/event.

Downloads

219

Readme

@hanzo/sentinel

The client for Sentinel, Hanzo Cloud's error plane, at /v1/sentinel.

@hanzo/event is the write side: it reports errors to the ingest endpoint its DSN names. This is the read side — the fifteen operations that list issues, resolve them, read the captured errors behind them, manage the projects that receive them, and query logs, rates, traces and aggregates. Neither package imports the other.

pnpm add @hanzo/sentinel
import { createSentinel } from '@hanzo/sentinel'

const sentinel = createSentinel({ token })

const { items = [] } = await sentinel.issues({ period: '24h', status: 'unresolved' })
for (const issue of items) console.log(issue.count, issue.type, issue.value)

await sentinel.updateIssue(items[0].id!, { status: 'resolved' })

The credential

Sentinel reads are session-authenticated. Pass a token and it rides as Authorization: Bearer; omit it in a browser on a Hanzo origin and the session cookie carries the request instead.

A publishable pk- key does not belong here. It can write to the ingest endpoint and read nothing, so the face refuses it.

Operations

| Call | Address | | --- | --- | | issues(query?) | GET /v1/sentinel/issues | | issue(id) | GET /v1/sentinel/issues/{id} | | updateIssue(id, change) | PUT /v1/sentinel/issues/{id} | | issueEvents(id, query) | GET /v1/sentinel/issues/{id}/events | | event(id, query) | GET /v1/sentinel/events/{id} | | projects() | GET /v1/sentinel/projects | | createProject(draft) | POST /v1/sentinel/projects | | project(id) | GET /v1/sentinel/projects/{id} | | deleteProject(id) | DELETE /v1/sentinel/projects/{id} | | rotateKey(id) | POST /v1/sentinel/projects/{id}/keys/rotate | | logs(query) | GET /v1/sentinel/logs | | stats(query) | GET /v1/sentinel/stats | | traces(query) | GET /v1/sentinel/traces | | trace(id, query) | GET /v1/sentinel/traces/{id} | | discover(query) | POST /v1/sentinel/discover |

Each returns what the answer envelope carried, not the envelope — the envelope's own status says nothing the HTTP status has not already said.

Refusals

A non-2xx answer is a throw, never an undefined that reads as an empty result.

import { SentinelError } from '@hanzo/sentinel'

try {
  await sentinel.issues()
} catch (error) {
  if (error instanceof SentinelError && error.code === 'forbidden') signIn()
}

status is the HTTP status, code is the face's own word for the refusal (forbidden, not_found) or empty when it did not give one, and body is what came back — parsed when it was JSON, the raw text when the edge answered 404 page not found instead.

Projects and DSNs

projects(), createProject() and rotateKey() each answer with the project's freshly-derived dsn. Hand that string to @hanzo/event: the DSN is the one place an ingest address is spelled.

const project = await sentinel.createProject({ name: 'hanzo-app', platform: 'javascript' })
project.dsn // https://<key>@api.hanzo.ai/v1/event/<projectId>

Rotating retires every key below the new one, so a surface still holding the old DSN stops being able to report until it is redeployed with the new one.