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

@zumer/snapeye

v0.1.0

Published

A tiny bridge that lets a coding agent see your running web app. snapDOM client + a two-endpoint HTTP handler — that's it.

Readme

SnapEye

SnapEye gives an AI coding agent eyes on a running web app.

It is a tiny localhost bridge:

  1. The browser captures DOM with @zumer/snapdom.
  2. The browser POSTs a PNG to the dev server.
  3. The server writes .snapeye/<name>.png.
  4. The agent reads that PNG and keeps editing.

No bundled browser automation. No baseline diffing. No framework runtime. Just a client helper and a two-endpoint Node HTTP handler.

For an agent, this is not a full visual testing stack; it is the cheap feedback loop that catches the common "I changed UI but cannot see it" case while leaving navigation, clicks, auth, and pixel-perfect testing to the host project.

Agent Contract

You are an AI coding agent in a repo with SnapEye wired into the dev app. Your job is to trigger captures, read the PNGs, and use what you see to iterate on UI work.

The server owns two POST endpoints under /__snapeye__:

| Method | Path | Body | Side effect | | ------ | -------------------------- | ------------ | ------------------------------- | | POST | /__snapeye__/snap?name=X | image/png | Writes <dir>/X.png | | POST | /__snapeye__/log | text/plain | Prints stdout as [browser] ... |

name is sanitized to [a-z0-9._-]; invalid characters become _. Missing names become snap-<timestamp>. <dir> defaults to .snapeye unless the server config overrides it.

This is the stable contract. Anything that produces these POSTs is a valid client.

Fast Path

If SnapEye is already installed in the repo:

open "http://localhost:8080/?snap=home"

Then read:

.snapeye/home.png

If the PNG does not appear, inspect the dev server stdout. Browser console output should be mirrored as [browser] ... lines when forwardConsole is enabled.

Install

npm install --save-dev @zumer/snapeye @zumer/snapdom

@zumer/snapdom is a required peer dependency.

Browser Setup

Add this somewhere in the app's client bundle:

import { snapdom } from '@zumer/snapdom'
import { attachSnapEye } from '@zumer/snapeye/client'

attachSnapEye({ snapdom })

This attaches:

window.snapeye.snap(name, target?)
window.snapeye.log(level, ...args)
window.snapeye.options

Server Setup

Mount the handler before the app's own routing:

import { createServer } from 'node:http'
import { createSnapEyeHandler } from '@zumer/snapeye/server'

const snapEye = createSnapEyeHandler({ dir: '.snapeye' })

createServer(async (req, res) => {
  if (await snapEye(req, res)) return
  // host app routing goes here
}).listen(8080)

The handler signature is:

(req: import('node:http').IncomingMessage,
 res: import('node:http').ServerResponse) => Promise<boolean>

It returns true when it handled the request.

Trigger Captures

Use whichever path is available in the environment.

# URL trigger, useful for agents
open "http://localhost:8080/?snap=home"
// Programmatic capture
await window.snapeye.snap('hero')
await window.snapeye.snap('checkout', document.querySelector('.checkout'))
Shift + S captures the default target.

After capture, read .snapeye/<name>.png.

Client Options

attachSnapEye({
  snapdom,                         // required
  endpoint: '/__snapeye__',        // must match server prefix
  autoOnQuery: true,               // ?snap=foo captures after load
  forwardConsole: true,            // mirrors console.* to /log
  errorOverlay: true,              // top bar for errors/rejections
  defaultTarget: () => document.documentElement,
  hideSelectors: ['.dev-only'],    // temporarily hidden during capture
  snapdomOptions: { dpr: 1, scale: 1 },
  hotkey: 'S'                      // Shift + S; set null to disable
})

Server Options

createSnapEyeHandler({
  dir: '.snapeye',
  prefix: '/__snapeye__',
  log: (line) => console.log(line), // pass null to silence
  onSnap: ({ name, path, bytes }) => {},
  onLog: ({ line }) => {}
})

Framework Integration

Vite

import { createSnapEyeHandler } from '@zumer/snapeye/server'

export default {
  plugins: [{
    name: 'snapeye',
    configureServer (server) {
      const handler = createSnapEyeHandler({ dir: '.snapeye' })
      server.middlewares.use(async (req, res, next) => {
        if (!(await handler(req, res))) next()
      })
    }
  }]
}

Express / Connect

const snapEye = createSnapEyeHandler({ dir: '.snapeye' })

app.use(async (req, res, next) => {
  if (!(await snapEye(req, res))) next()
})

Do not pass Fetch Request/Response objects directly to this handler. It expects Node IncomingMessage and ServerResponse. Fetch-native runtimes need a small adapter or a separate handler implementation.

Agent Loop

Use this loop when editing UI:

# 1. Start fresh
rm -rf .snapeye
npm run dev > /tmp/dev.log 2>&1 &

# 2. Trigger the route
open "http://localhost:8080/?snap=home"

# 3. Read the result
# .snapeye/home.png

If the capture is stale or missing:

  • Check /tmp/dev.log for [browser] ... output.
  • Confirm the app loaded the client setup.
  • Confirm server prefix matches client endpoint.
  • Confirm the output directory is the one you are reading.

SnapEye intentionally does not navigate your app. For multi-route captures, drive routing yourself and call window.snapeye.snap(name) after each route has rendered.

What It Does Not Do

  • No visual regression baseline. Use @zumer/snapdiff or a recipe.
  • No bundled headless mode. Drive Playwright/Puppeteer yourself if needed.
  • No authentication. Mount on localhost/dev servers only.
  • No Fetch-native server adapter in core.
  • No TypeScript declarations yet.

Recipes

The core stays small. Optional patterns live in RECIPES.md:

  • agent-map: annotated Set-of-Mark PNG plus element metadata JSON.
  • snapdiff: compare the current capture with the previous one.
  • namespace: isolate multiple agents writing to the same server.

License

MIT - (c) Zumerlab

For Humans

SnapEye is useful if you want an agent to verify real rendered UI without asking you to describe the screen: wire the client into your dev page, mount the server handler on localhost, open a route with ?snap=name, and the agent gets a PNG it can inspect; if you need pixel-perfect browser screenshots, visual regression history, auth, or navigation orchestration, use Playwright or a dedicated visual-testing tool alongside SnapEye rather than expanding the core.