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

@watchman-tower/app-agent-nodejs

v0.0.12

Published

Watchman Tower App Agent for Node.js applications

Readme

@watchman-tower/app-agent-nodejs

Lightweight Watchman Tower App Agent for Node.js and Express applications.

The agent collects application-level telemetry from your service and sends it to Watchman Tower with an App Agent token. It is designed to be safe for production: telemetry is aggregated in memory, sent on a timer, and ingest failures are swallowed so the monitored application keeps running.

Preview Access

App Agent is currently in a test phase. If you want to try it with your Watchman Tower account, contact [email protected].

Features

  • Aggregated Express HTTP metrics
  • Express error capture middleware
  • Optional Node.js runtime telemetry
  • Static/noise path filtering
  • Request sampling
  • Safe defaults for production use
  • TypeScript definitions and JSDoc autocomplete
  • Runtime config validation for JavaScript users

Installation

npm install @watchman-tower/app-agent-nodejs

You need a Watchman Tower App Agent token before telemetry can be accepted. App Agent access is currently limited while the feature is in testing; email [email protected] to request access.

Peer dependency:

npm install express

Quick Start

const express = require("express");
const { AppAgent } = require("@watchman-tower/app-agent-nodejs");

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "api",
  env: process.env.NODE_ENV || "production",
  runtimeTelemetry: {
    enabled: true,
  },
});

const app = express();

app.use(AppAgent.express());

app.get("/health", (_req, res) => {
  res.json({ ok: true });
});

app.use(AppAgent.errorHandler());

Place AppAgent.express() before your routes. Place AppAgent.errorHandler() after your routes and before your application's final error handler.

TypeScript Usage

import express from "express";
import { AppAgent } from "@watchman-tower/app-agent-nodejs";

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN!,
  service: "api",
  env: process.env.NODE_ENV || "production",
  flushIntervalSec: 60,
  runtimeTelemetry: {
    enabled: true,
    intervalSec: 60,
  },
});

const app = express();

app.use(AppAgent.express());

app.get("/health", (_req, res) => {
  res.json({ ok: true });
});

app.use(AppAgent.errorHandler());

Runtime-only Usage

If a project only needs process/runtime telemetry and should not collect Express HTTP metrics, disable HTTP telemetry:

const { AppAgent } = require("@watchman-tower/app-agent-nodejs");

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "worker",
  env: process.env.NODE_ENV || "production",
  http: {
    enabled: false,
  },
  runtimeTelemetry: {
    enabled: true,
  },
});

When http.enabled is false, AppAgent.express() returns a no-op middleware and no type: "http" payloads are produced.

JavaScript Type Checking

For JavaScript projects, enable editor-level config validation with // @ts-check and a JSDoc type annotation:

// @ts-check

const { AppAgent } = require("@watchman-tower/app-agent-nodejs");

/** @type {import("@watchman-tower/app-agent-nodejs").AppAgentConfig} */
const appAgentConfig = {
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "api",
  env: "production",
  runtimeTelemetry: {
    enabled: true,
  },
};

AppAgent.init(appAgentConfig);

Unknown options are also rejected at runtime:

[AppAgent] Unknown runtimeTelemetry option(s): location.

Configuration

| Option | Required | Default | Description | | --- | --- | --- | --- | | token | Yes | - | Watchman Tower App Agent token. Use an environment variable. | | service | Yes | - | Stable service name shown in Watchman Tower, such as api or checkout-api. | | env | No | process.env.NODE_ENV || "development" | Environment label sent with each payload. | | flushIntervalSec | No | 60 | HTTP/error flush interval in seconds. Must be an integer between 10 and 3600. | | sampleRate | No | 1 | Request sampling ratio from 0 to 1. | | timeoutMs | No | 2000 | Ingest request timeout in milliseconds. Clamped between 100 and 30000. | | debug | No | false | Enables App Agent console logs. Tokens are never logged. | | http.enabled | No | true | Enables Express HTTP request metrics. Set to false for runtime-only telemetry. | | scannerTraffic.enabled | No | true | Classifies common scanner/probe paths before they pollute application telemetry. | | scannerTraffic.action | No | "drop" | "drop" excludes scanner traffic from HTTP/error telemetry. "metric" also reports aggregated security telemetry. | | scannerTraffic.report | No | false | Sends scanner traffic as a type: "security" payload. | | scannerTraffic.patterns | No | - | Additional path prefixes or regular expressions treated as scanner/probe traffic. | | maxRoutes | No | 500 | Maximum unique route/status keys kept per flush interval. | | maxRouteLength | No | 160 | Maximum normalized route length. | | ignorePaths | No | Built-in static/noise paths | Additional paths ignored before sampling and collection. Values are merged with defaults. | | maxErrors | No | 100 | Maximum unique error fingerprints kept per flush interval. | | maxErrorMessageLength | No | 500 | Maximum captured error message length. | | maxErrorStackLength | No | 4000 | Maximum captured stack length when stack capture is enabled. | | runtimeTelemetry.enabled | No | false | Enables process/runtime telemetry collection. | | runtimeTelemetry.intervalSec | No | 60 | Runtime telemetry interval in seconds. Must be an integer between 10 and 3600. | | runtimeTelemetry.eventLoopDelay | No | true | Enables Node.js event loop delay histogram collection. | | runtimeTelemetry.getActiveChecks | No | - | Optional callback returning active work count. | | runtimeTelemetry.getQueuedJobs | No | - | Optional callback returning queued job count. | | runtimeTelemetry.getCompletedChecks | No | - | Optional callback returning completed work count. | | runtimeTelemetry.getFailedChecks | No | - | Optional callback returning failed work count. | | runtimeTelemetry.getRedisStatus | No | - | Optional callback returning Redis/client status. |

HTTP Metrics

AppAgent.express() records aggregated request metrics:

  • HTTP method
  • normalized route
  • response status
  • request count
  • error count
  • duration buckets

Express route patterns are preferred when available, for example /users/:id. Fallback paths mask numeric IDs, UUIDs, long hex/base64-like values, emails, and very long path segments.

Error Capture

AppAgent.errorHandler() observes Express errors and always calls next(err). It does not generate a response, swallow errors, or replace your application's own error handler.

app.use(AppAgent.errorHandler({
  captureStack: false,
}));

By default, client errors that pass through Express error middleware are not reported as application exceptions. This keeps 404/401/403 style responses and common scanner noise out of the Exceptions view. Server errors are still captured:

4xx -> not captured as an exception by default
5xx -> captured as an application exception

If your application intentionally throws meaningful 4xx errors and you want to capture them as exceptions, opt in:

app.use(AppAgent.errorHandler({
  captureClientErrors: true,
}));

By default, error capture sends aggregate-safe fields:

  • method
  • normalized route
  • status
  • error name
  • truncated message
  • fingerprint
  • count

Stack traces are disabled by default. Request body, headers, and query strings are never captured.

Scanner Traffic

The agent classifies common internet scanner/probe requests before they pollute application telemetry. Examples include WordPress probes, random PHP files, .env, .git, phpMyAdmin, Adminer, backup files, and similar exploit scans.

Default behavior is to drop scanner traffic from HTTP and error telemetry:

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "api",
  scannerTraffic: {
    enabled: true,
    action: "drop",
  },
});

To also send aggregated scanner traffic as a security signal:

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "api",
  scannerTraffic: {
    action: "metric",
  },
});

You can add project-specific scanner patterns without replacing the built-in patterns:

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "api",
  scannerTraffic: {
    patterns: [
      /^\/legacy-admin(?:\/|$)/,
      "/private-config",
    ],
  },
});

Runtime Telemetry

Runtime telemetry is optional and runs on a separate unref() timer. It captures process-level signals without touching request/response handling:

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "api",
  runtimeTelemetry: {
    enabled: true,
    getQueuedJobs: () => queue.pendingCount(),
    getRedisStatus: () => redis.status,
  },
});

Collected runtime fields:

  • process PID, uptime, and Node.js version
  • memory usage
  • CPU delta for the interval
  • event loop delay percentiles
  • optional workload counters
  • optional Redis/client status

User callbacks are wrapped in try/catch; failures are reported as null and never affect the host application.

Ignored Paths

The agent ignores common static and crawler paths by default:

/cdn-cgi
/favicon.ico
/robots.txt
/sitemap.xml
/manifest.json
/css
/js
/img
/images
/fonts
/assets
/static
/public
/_next
/build
/dist

Custom ignorePaths are appended to this default list:

AppAgent.init({
  token: process.env.WT_APP_AGENT_TOKEN,
  service: "api",
  ignorePaths: [
    "/admin/assets",
    /^\/internal(?:\/|$)/,
  ],
});

Payload Shape

HTTP payloads are aggregated and sent like this:

{
  "v": 1,
  "type": "http",
  "application": {
    "service": "api",
    "env": "production",
    "runtime": "nodejs"
  },
  "agent": {
    "name": "app-agent-nodejs",
    "version": "0.0.5"
  },
  "timestamp": 1786275759631,
  "interval_sec": 60,
  "sample_rate": 1,
  "dropped_count": 0,
  "metrics": []
}

Runtime payloads use runtime.process, not infrastructure-specific names:

{
  "v": 1,
  "type": "runtime",
  "runtime": {
    "process": {
      "pid": 12345,
      "uptimeSec": 3600,
      "nodeVersion": "v24.10.0"
    },
    "memory": {},
    "cpu": {},
    "eventLoop": {},
    "workload": {},
    "redis": {}
  }
}

When scannerTraffic.action is "metric" or scannerTraffic.report is true, scanner traffic is sent separately from HTTP and exception telemetry:

{
  "v": 1,
  "type": "security",
  "security": {
    "scannerTraffic": [
      {
        "category": "wordpress_probe",
        "count": 12,
        "samplePaths": ["/wp-admin", "/wp-login.php"],
        "methods": { "GET": 12 },
        "statuses": { "404": 12 }
      }
    ]
  }
}

Watchman Tower

Use Watchman Tower to create and manage the App Agent identity and token used by this package.

Production Notes

  • Keep the token in an environment variable.
  • Do not enable debug in normal production traffic unless you are diagnosing an issue.
  • Ingest failures are swallowed so the monitored application keeps running.
  • The ingest endpoint is managed by the package and cannot be changed through AppAgent.init().
  • Unknown config options throw during AppAgent.init().
  • flushIntervalSec and runtimeTelemetry.intervalSec must be between 10 and 3600.
  • Duration buckets only include non-zero buckets.
  • The open-ended duration bucket is serialized as "+Inf".
  • SIGINT and SIGTERM trigger a final flush before process exit.

Development

npm install
npm run build

The package is written in TypeScript and publishes the compiled dist directory.

License

MIT