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

@edngibson/reporter

v0.1.0

Published

Stateless reporter for the agent-dock Layer 1 rendezvous: Claude Code hooks + deadman heartbeat + client-side encryption. Zero runtime dependencies (Node >= 18 stdlib only).

Readme

agent-dock reporter

The Layer 1 write path: a stateless reporter that tells the agent-dock rendezvous which agents are running on this host, so the dock — on any machine — sees the merged global fleet.

One small Node script (reporter.mjs), zero runtime dependencies — Node ≥ 18 stdlib only. It's a script, not a compiled binary, on purpose: every host that runs agents already runs Claude Code, which already requires Node, so the reporter adds no new runtime and no cross-compile. Copy reporter/ to the host and you're done.

What it does

Both triggers run the same path — enumerate this host's agents via claude agents --json, encrypt the sensitive fields, and POST a full snapshot to the rendezvous ingest function:

  • hook (one-shot) — wired to Claude Code lifecycle hooks. Low-latency: a transition is reflected almost immediately. Full snapshots mean a finished agent disappears the instant its Stop hook fires, rather than waiting out the TTL.
  • heartbeat (long-running) — re-snapshots every 90 s (ADK_HEARTBEAT_SECONDS). The deadman: it proves the host is alive so the server can TTL-expire a silently-dead host (crash / kill -9 / OOM / power loss), and it self-heals any hook push that got dropped. This is the only periodic traffic.

Zero-knowledge (decision I): label, cwd, and current_action are encrypted client-side with AES-256-GCM using a key the rendezvous never sees. Status, kind, and timestamps travel in clear so the server can route and expire without decrypting. A leaked server row is unreadable without the key.

Onboard a machine — pair (recommended)

Status: the pair / unpair CLI and the dock's bundle-mint command (mint_pairing_bundle) are implemented; still pending are publishing @edngibson/reporter to npm and wiring the dock's "+ Add a machine" button into the UI. Until then, use the manual setup below — this section describes the target one-paste experience.

One command does everything the manual sections below do by hand — enroll, write the config, install the Claude Code hooks, and start the heartbeat service:

npx -y @edngibson/reporter pair <bundle>

Get <bundle> from the dock's "+ Add a machine" button: it mints a single-use, 15-minute pairing code and shows the adk1_… string to paste plus a four-word fingerprint. pair prints the same fingerprint on this host — eyeball that the two match (this catches a bundle swapped in transit through Slack/email/SSH), and you're on the dock within seconds. The bundle carries the rendezvous URL, the pairing code, and the encryption key out-of-band; the server never sees the key.

pair is idempotent — re-running refreshes in place (one hook per event, one service). It installs the right per-user service for the OS, no sudo: systemd --user (+ loginctl enable-linger), a launchd LaunchAgent, or a Task Scheduler ONLOGON task. The hooks it installs are async (a slow or down rendezvous never blocks the agent) and cover the lifecycle events — SessionStart, Stop, SubagentStop, Notification; current_action is refreshed by the heartbeat.

npx -y @edngibson/reporter unpair   # reverse it: stop the service, strip the hooks, delete the config

The sections below document the manual equivalents — pair automates all of them. Reach for them to customize beyond what pair does, or where npx isn't available.

Configuration

Config comes from env vars (which override an optional file at ~/.agent-dock/reporter.json, or $ADK_CONFIG). The token and key arrive out-of-band from the dock's pairing bundle — they are never fetched from the server.

| Env var | File key | Required | Meaning | |---|---|---|---| | ADK_SUPABASE_URL | supabaseUrl | one of these | Project URL, e.g. https://<ref>.supabase.co (ingest URL is derived) | | ADK_INGEST_URL | ingestUrl | one of these | Full ingest URL (overrides the derived one) | | ADK_REPORTER_TOKEN | token | ✅ | Opaque per-host token (adk_…) from pairing | | ADK_ENC_KEY | encKey | ✅ | Shared key, base64 of 32 bytes | | ADK_HEARTBEAT_SECONDS | heartbeatSeconds | — | Snapshot interval, default 90 | | ADK_CLAUDE_BIN | claudeBin | — | Claude CLI command, default claude | | ADK_APIKEY | apikey | — | Optional gateway apikey header (not needed for the current verify_jwt=false ingest) | | ADK_CONFIG | — | — | Path to the config file |

Generate a fresh encryption key (put the same value in the dock and every host you pair):

node reporter.mjs gen-key      # prints base64 of 32 random bytes

Example ~/.agent-dock/reporter.json:

{
  "supabaseUrl": "https://txwhijfclpozpsukmypm.supabase.co",
  "token": "adk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "encKey": "base64-of-32-bytes-same-as-the-dock"
}

Install — Claude Code hooks (every host)

Add to ~/.claude/settings.json (user-level → covers all projects). The reporter runs as a type:"command" hook with async:true so a slow or down rendezvous never blocks or fails the agent (type:"http" hooks block — that's why this is a command hook). Use an absolute path to reporter.mjs.

{
  "hooks": {
    "SessionStart":  [{ "hooks": [{ "type": "command", "command": "node /opt/agent-dock/reporter/reporter.mjs hook", "async": true }] }],
    "Stop":          [{ "hooks": [{ "type": "command", "command": "node /opt/agent-dock/reporter/reporter.mjs hook", "async": true }] }],
    "SubagentStop":  [{ "hooks": [{ "type": "command", "command": "node /opt/agent-dock/reporter/reporter.mjs hook", "async": true }] }],
    "Notification":  [{ "hooks": [{ "type": "command", "command": "node /opt/agent-dock/reporter/reporter.mjs hook", "async": true }] }],
    "PreToolUse":    [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node /opt/agent-dock/reporter/reporter.mjs hook", "async": true }] }],
    "PostToolUse":   [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node /opt/agent-dock/reporter/reporter.mjs hook", "async": true }] }]
  }
}

On Windows use the absolute path, e.g. node C:\\opt\\agent-dock\\reporter\\reporter.mjs hook.

Coverage notes: SessionStart/Stop/SubagentStop/Pre|PostToolUse fire in both interactive and background sessions. Notification (blocked-waiting) is interactive-only; in headless sessions the blocked state is caught by the next snapshot, since claude agents --json reports state:"blocked". current_action is best-effort — PreToolUse names the running tool; it reverts to nothing on the next heartbeat.

Install — heartbeat service (every host)

Linux / VPS (systemd)

/etc/systemd/system/agent-dock-reporter.service:

[Unit]
Description=agent-dock reporter (deadman heartbeat)
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/node /opt/agent-dock/reporter/reporter.mjs heartbeat
Environment=ADK_SUPABASE_URL=https://txwhijfclpozpsukmypm.supabase.co
Environment=ADK_REPORTER_TOKEN=adk_xxxxxxxx
Environment=ADK_ENC_KEY=base64-of-32-bytes
Restart=always
RestartSec=10
User=youruser

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now agent-dock-reporter
journalctl -u agent-dock-reporter -f      # watch snapshots

Windows (laptop)

Run the heartbeat at logon via Task Scheduler:

$action  = New-ScheduledTaskAction -Execute "node" `
  -Argument "C:\opt\agent-dock\reporter\reporter.mjs heartbeat"
$trigger = New-ScheduledTaskTrigger -AtLogOn
Register-ScheduledTask -TaskName "agent-dock-reporter" `
  -Action $action -Trigger $trigger -RunLevel Limited

Put the config in %USERPROFILE%\.agent-dock\reporter.json (Task Scheduler doesn't inherit a shell's env vars).

Develop

node --test          # unit tests (status/label mapping, snapshot, crypto round-trip + KAT)
node reporter.mjs gen-key
ADK_CLAUDE_BIN=./test-fixtures/fake-claude node reporter.mjs heartbeat   # point at a stub

The AES-256-GCM wire envelope is base64( nonce(12) ‖ ciphertext ‖ tag(16) ). The dock decrypts the inverse with the Rust aes-gcm crate (Task 3); reporter.test.mjs pins a known-answer vector so the two stay byte-compatible.