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

snagd

v0.1.3

Published

snagd: the local Snag daemon, and `snagd init`, which sets Snag up on an app.

Readme

snagd

snagd — the local daemon. Owns .snag/ and the SQLite index derived from it.

Licence: Apache-2.0 · Spec: docs/02-BACKEND-SPEC.md §6, §7, §10, §11. Locked decisions live in docs/00-MASTER-BUILD-PLAN.md §7 — do not relitigate them.

npx snagd [--root <dir>] [--port <n>] [--web-root <dir>] [--no-map] [--no-watch]

Detects the git root, creates .snag/ (including the shipped .gitignore from D8), builds the repo map, starts the file watcher, binds the first free port in 7654–7664, writes it to .snag/.port, and prints the token.

The four rules everything here follows

  1. Files are the API. .snag/ is the contract; index.db is a rebuildable index. Delete it and restart — nothing is lost (D7, doc 02 §6.2).
  2. Every route except GET /health requires the token. Any web page can otherwise reach http://127.0.0.1:<port> (doc 02 §14, gotcha 21). The token is generated on first run into ~/.snag/token, mode 0600.
  3. Nothing hard-codes 7654. The extension has no filesystem access, so it probes GET /health across the range; the daemon binds the first free port and reports it (doc 02 §7.3).
  4. The transport is an interface. Transport in src/transport/index.ts; WebSocketTransport is one implementation, the §7.4 native-messaging fallback would be a second, and LoopbackTransport is the third.

Layout

src/
├─ cli.ts             npx snagd — git root, .snag/, map, watcher, token, port
├─ server.ts          Fastify assembly; every route in doc 02 §7.1
├─ auth.ts            ~/.snag/token, the global preHandler guard
├─ health.ts          GET /health — unauthenticated, counts only, never captures
├─ port.ts            first free port in 7654-7664
├─ paths.ts           the .snag/ layout, blob kinds and content types
├─ context.ts         Daemon — one root, one store, one index, one transport
├─ watcher.ts         debounced .snag/ watcher → reindex
├─ transport/         { id, type, payload }; one JSON object per message
├─ routes/            capture · blob · threads · tasks · plan · map · tokens · verify · export
├─ store/             files · db · rebuild · search · task-md · counter
├─ derive/            severity · kind · enrich · relations · threads  (no LLM calls)
├─ verify/            run · diff — the §10 loop
└─ export/            the Exporter interface and shared mapping (adapters live in integrations/)

Things that look like details and are not

  • Severity rule 3 must precede the catch-all, or p3 is unreachable while still being a shipped token and a visible chip. derive/severity.ts keeps the list as ordered data and test/severity.test.ts proves p3 fires.
  • GET /blob/:capId/:kind is load-bearing. The extension cannot read file://, so every byte it renders comes through it — with the right content type, and with paths that cannot escape the project root.
  • Binary blobs arrive as HTTP multipart, never over the WebSocket.
  • resolved is only ever set by Verify. applyTransition(..., 'verify') is called from verify/run.ts and nowhere else; the HTTP route cannot spoof the actor. With plan.verify_on_resolve: false, in_progress → resolved is legal and records verified: false.
  • A still-unresolvable anchor is needs_review, never a guess.
  • Task labels are global, never reused, never renumbered. Un-merging mints new ones (store/counter.ts).
  • Token counts are real or explicitly approximate. Offline they fall back to a character heuristic and the response says so, so the UI can render ≈.

Optional dependencies, resolved at runtime

@frmwrkd/snag-plan is source-available (D12) and is not a dependency of this package; POST /plan loads it dynamically and answers 501 plan_unavailable when it is absent, with a deterministic one-task-per-capture fallback available on request. The three exporters in integrations/ load the same way.

Development

pnpm --filter snagd build       # tsc
pnpm --filter snagd typecheck   # src + test
pnpm --filter snagd test        # vitest