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

vitals-node

v1.1.2

Published

Multi-framework health monitoring & system metrics for NestJS, Express, and Bun

Readme

vitals-node

npm version License: MIT TypeScript

Multi-framework health monitoring and AI agent integration for Node.js. Supports NestJS, Express, and Bun with zero-config setup.


Features

  • Multi-framework — NestJS, Express, Bun
  • Mode systemmonitor (health/metrics) · agent (AI integration) · both
  • Real-time dashboard — Charts, dark mode, route panel
  • System metrics — CPU, Memory, Disk monitoring
  • Route discovery — Auto-detect all registered API routes
  • AI schema analysis — Claude or Ollama reads source code and infers request schemas
  • Manual registry push — POST route list + schemas to a central server on demand
  • call_api built-in tool — AI can invoke any route on this service directly
  • Alert notifications — LINE, Email, Telegram
  • Prometheus export — Ready for Grafana/Prometheus
  • Prisma integration — Database query monitoring + auto-generate agent tools
  • Env auto-config — Zero code change via VITALS_* environment variables
  • TypeScript first — Full type safety

Installation

npm install vitals-node

Quick Start

NestJS

import { Module } from '@nestjs/common';
import { VitalsPulseModule } from 'vitals-node';

@Module({
  imports: [
    VitalsPulseModule.forRoot({
      mode: 'both',
      agent: { enabled: true, apiKey: process.env.VITALS_AGENT_API_KEY }
    })
  ]
})
export class AppModule {}

With mode: 'both' NestJS registers two controllers automatically:

  • HealthController — mounts at /health
  • AgentsController — mounts at /agents

Express

import express from 'express';
import { expressPulse } from 'vitals-node/express';

const app = express();
app.use(express.json());

app.use('/health', expressPulse({ app, mode: 'monitor' }));
app.use('/agents', expressPulse({ app, mode: 'agent', agent: { apiKey: process.env.VITALS_AGENT_API_KEY } }));

app.listen(3000);

Bun

import { bunPulse } from 'vitals-node/bun';

const pulse = bunPulse({ mode: 'both' });

Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname.startsWith('/health')) return pulse.handler(req);
    return new Response('OK');
  }
});

Mode System

| Mode | What it enables | Metrics interval | |---|---|---| | monitor | health, metrics, dashboard, prometheus, alerts | runs | | agent | route discovery, AI tools, registry push, call_api | skipped (saves resources) | | both | all features (default) | runs |

In NestJS mode: 'both' creates two separate controllers: HealthController at /health and AgentsController at /agents. In Express/Bun you mount each middleware at the path you choose.


Endpoints

Monitor endpoints

Available when mode: 'monitor' or 'both' (mounted at your chosen base path, e.g. /health):

| Endpoint | Description | |---|---| | GET /health | Liveness probe | | GET /health/detailed | Full system metrics + health checks | | GET /health/dashboard | Real-time HTML dashboard | | GET /health/metrics | Prometheus format | | GET /health/prometheus | Prometheus format (alias) | | GET /health/history | Metrics history | | GET /health/alerts | Alert history | | POST /health/test-alerts | Test notification channels |

Agent endpoints

Available when mode: 'agent' or 'both'. NestJS uses /agents/*; Express/Bun use the path you mount with mode: 'agent':

| Endpoint | Description | |---|---| | GET /agents/routes | All discovered routes with inferred schemas | | GET /agents/tools | Agent tool schemas (Anthropic format) | | POST /agents/invoke | Invoke an agent tool { name, input } | | POST /agents/registry/push | Manually push registry to a webhook |

All agent endpoints require Authorization: Bearer <apiKey> when agent.apiKey is set.

In Express/Bun with mode: 'both', agent endpoints are nested under the monitor base path as /health/agent/tools, /health/agent/invoke, and /health/agent/registry/push.


NestJS Controllers

VitalsPulseModule.forRoot() registers controllers based on mode:

mode: 'monitor' → HealthController  at /health
mode: 'agent'   → AgentsController at /agents
mode: 'both'    → both controllers (default)

You can import the controllers directly for custom configuration:

import { HealthController, AgentsController, PULSE_MONITOR } from 'vitals-node';

Environment Variables

Target projects only need VITALS_AGENT_API_KEY — all other config is passed in request bodies.

| Variable | Description | |---|---| | VITALS_MODE | monitor · agent · both | | VITALS_PATH | Base path override | | VITALS_AGENT_ENABLED | true to enable agent endpoints | | VITALS_AGENT_API_KEY | Bearer token for /agents/* protection |


Manual Registry Push

Registry push is triggered on demand via POST /agents/registry/push. No config is stored in the target project — all parameters come from the request body.

POST /agents/registry/push
Authorization: Bearer <apiKey>

{
  "webhook": "https://central-server.com/register",  // optional — omit to get data in response only
  "groupId": "my-company",                           // optional — group multiple services together
  "projectId": "my-service",
  "projectPath": "/api",                             // optional — override auto-detected base path
  "tags": "crm,billing",                             // optional — help AI categorize this service
  "apiKey": "central-server-bearer-token",
  "baseUrl": "https://my-service.example.com",
  "schemaAnalysis": {                                 // optional — run AI schema analysis before push
    "provider": "anthropic",
    "apiKey": "sk-ant-xxx",
    "model": "claude-haiku-4-5-20251001"
  }
}

Response always includes the full payload (same data sent to webhook):

{
  "success": true,
  "groupId": "my-company",
  "projectId": "my-service",
  "baseUrl": "https://my-service.example.com",
  "projectPath": "/api",
  "tags": "crm,billing",
  "routes": [...],
  "agentTools": [...],
  "registeredAt": 1748247600000
}

If webhook is omitted the push is skipped but the response still contains the full payload — useful for inspecting what would be sent.


AI Schema Analysis

Pass schemaAnalysis in the POST /agents/registry/push body to run schema inference before pushing. No API keys are stored in the target project.

Using Anthropic Claude

{
  "schemaAnalysis": {
    "provider": "anthropic",
    "apiKey": "sk-ant-xxx",
    "model": "claude-haiku-4-5-20251001",
    "concurrency": 5,
    "timeout": 30000
  }
}

Using Ollama (local LLM)

{
  "schemaAnalysis": {
    "provider": "ollama",
    "baseUrl": "http://localhost:11434",
    "model": "llama3.2",
    "concurrency": 1,
    "timeout": 120000
  }
}

How it works

  1. Routes are discovered from the running app
  2. vitals finds each handler's source file
  3. Source code is sent to the LLM in batches
  4. LLM returns body / params / query schema as JSON
  5. Schemas are included in /agents/routes and the push payload

Example schema output

{
  "method": "POST",
  "path": "/credits/adjust",
  "handler": "adjustCredit",
  "schema": {
    "description": "Adjust credit balance for a user account",
    "body": {
      "userId":  { "type": "string", "description": "Target user ID",                "required": true  },
      "amount":  { "type": "number", "description": "Amount — positive adds, negative deducts", "required": true  },
      "reason":  { "type": "string", "description": "Reason for adjustment",         "required": false }
    }
  }
}

call_api Built-in Tool

When vitals starts, it auto-registers a call_api tool that lets the AI invoke any route on the service directly.

POST /agents/invoke
Authorization: Bearer <apiKey>

{
  "name": "call_api",
  "input": {
    "method": "POST",
    "path": "/api/auth/login",
    "headers": { "x-api-key": "service-key" },
    "body": { "username": "admin", "password": "secret" }
  }
}

→ { "result": { "status": 200, "ok": true, "body": { "token": "..." } } }

Parameters

| Parameter | Type | Required | Description | |---|---|---|---| | method | string | yes | GET POST PUT PATCH DELETE | | path | string | yes | API path e.g. /api/users/123 | | body | object | no | Request body for POST/PUT/PATCH | | headers | object | no | Extra headers — use for auth cookies, API keys | | query | object | no | Query string params |

Handling cookie-protected routes

For endpoints behind @UseGuards(CookieAuthGuard), pass the cookie in headers:

{
  "name": "call_api",
  "input": {
    "method": "GET",
    "path": "/user/info/full",
    "headers": { "Cookie": "accessToken=<token_value>" }
  }
}

The AI can first call a login endpoint to obtain the token, then use it in subsequent calls.


AI Agent Flow

customer reports problem
  → chat → AI agent (Claude)
    → GET /agents/routes    — discover what APIs exist and their schemas
    → GET /agents/tools     — discover invokable diagnostic tools
    → POST /agents/invoke   — call the right tool or API to fix the problem
  → response to customer

Example: agent fixes a credit mismatch

// AI discovers routes and tools, then calls:
POST /agents/invoke
{ "name": "adjust_credit", "input": { "userId": "U123", "amount": 100 } }
→ { "result": { "success": true, "newBalance": 500 } }

// Or directly calls the API:
POST /agents/invoke
{ "name": "call_api", "input": { "method": "POST", "path": "/credits/adjust", "body": { "userId": "U123", "amount": 100 } } }
→ { "result": { "status": 200, "ok": true, "body": { "success": true } } }

Route Discovery

NestJS

Routes are captured automatically via OnApplicationBootstrap lifecycle hook.

Express

Discovery is lazy — routes registered after the middleware call are still captured.

app.use('/health', expressPulse({ app }));
// All routes registered below are still discovered
app.get('/api/orders', handler);

Fastify

monitor.discoverRoutes(fastifyApp, 'fastify');

Manual Registration

import { createMonitor } from 'vitals-node/core';

const monitor = createMonitor();

monitor.registerRoute({ method: 'GET', path: '/api/orders', handler: 'OrderController.list' });
monitor.registerRoutes([
  { method: 'POST', path: '/api/orders' },
  { method: 'PUT',  path: '/api/orders/:id/status' }
]);

// Manual schema annotation — overrides AI analysis for this route
monitor.annotateRoute('POST', '/api/orders', {
  description: 'Create a new order',
  body: {
    userId: { type: 'string', description: 'User ID', required: true },
    items:  { type: 'array',  description: 'Order items',  required: true }
  }
});

Configuration Reference

interface VitalsConfig {
  mode?: 'monitor' | 'agent' | 'both';  // default: 'both'
  path?: string;                          // default: '/health'
  dashboard?: boolean;                    // default: true
  collectInterval?: number;              // ms (default: 5000)
  timezone?: string;                     // default: 'Asia/Bangkok'
  prometheus?: boolean;                  // default: true

  alerts?: {
    thresholds?: {
      cpu?: number;          // default: 80%
      memory?: number;       // default: 90%
      disk?: number;         // default: 85%
      responseTime?: number; // default: 3000ms
    };
    cooldown?: number;       // ms between alerts (default: 5 min)
    line?:     { token: string; enabled?: boolean };
    email?:    { host: string; port: number; user: string; password: string; from: string; to: string[]; secure?: boolean };
    telegram?: { botToken: string; chatId: string; enabled?: boolean };
  };

  healthChecks?: HealthCheckDefinition[];

  agent?: {
    enabled?: boolean;        // must be true to expose /agent/* endpoints
    apiKey?: string;          // Bearer token protecting agent endpoints
    tools?: AgentToolDefinition[];
  };
}

schemaAnalysis and registry config are passed in the POST /agents/registry/push request body, not in startup config — this keeps secrets out of the target project's environment.


Custom Health Checks

monitor.addHealthCheck({
  name: 'redis',
  critical: true,
  timeout: 5000,
  check: async () => {
    const start = Date.now();
    try {
      await redis.ping();
      return { name: 'redis', status: 'healthy', responseTime: Date.now() - start };
    } catch (err) {
      return { name: 'redis', status: 'unhealthy', responseTime: Date.now() - start, error: err.message };
    }
  }
});

Agent Tools

Register custom tools that the AI can invoke via POST /agents/invoke:

import { getMonitor } from 'vitals-node/core';

getMonitor().registerAgentTool({
  name: 'adjust_credit',
  description: 'Adjust user credit balance',
  parameters: {
    userId: { type: 'string', description: 'User ID', required: true },
    amount: { type: 'number', description: 'Amount to adjust (+/-)', required: true }
  },
  handler: async ({ userId, amount }) =>
    creditService.adjust(String(userId), Number(amount))
});

Tool schemas follow the Anthropic input_schema format — compatible with Claude tool use directly.


Prisma Integration

import { PrismaClient } from '@prisma/client';
import { prismaMiddleware, createPrismaHealthCheck, createPrismaAgentTools } from 'vitals-node/prisma';
import { getMonitor } from 'vitals-node/core';

const prisma = new PrismaClient();

prisma.$use(prismaMiddleware({ slowQueryThreshold: 1000, logSlowQueries: true }));

const monitor = getMonitor();
monitor.addHealthCheck(createPrismaHealthCheck(prisma));

const tools = createPrismaAgentTools(prisma, {
  dmmf: Prisma.dmmf,
  models: ['User', 'CreditTransaction'],
  sensitiveFields: ['internalNote']
});
monitor.registerAgentTools(tools);

System Metrics

{
  "cpu":     { "usage": 45.5, "cores": 8, "model": "Intel...", "speed": 3.2 },
  "memory":  { "total": 16384, "used": 8192, "free": 8192, "percentage": 50 },
  "disk":    { "total": 512000, "used": 256000, "free": 256000, "percentage": 50 },
  "process": { "uptime": 86400, "pid": 1234, "nodeVersion": "v20.0.0", "memoryUsage": 128, "cpuUsage": 2.5 }
}

Event Handling

const monitor = getMonitor();

monitor.on('metrics:collected', (event) => console.log('Metrics:', event.data));
monitor.on('alert:triggered',   (event) => console.log('Alert!',   event.data));

Available events: metrics:collected · alert:triggered · alert:resolved · health:changed · database:slow-query · database:error


Prometheus / Grafana

scrape_configs:
  - job_name: 'my-app'
    static_configs:
      - targets: ['localhost:3000']
    metrics_path: '/health/metrics'

Alert Configuration

LINE Notify

alerts: { line: { token: 'YOUR_LINE_NOTIFY_TOKEN' } }

Telegram

alerts: { telegram: { botToken: 'BOT_TOKEN', chatId: 'CHAT_ID' } }

Email (SMTP)

alerts: { email: { host: 'smtp.gmail.com', port: 587, user: '...', password: '...', from: '...', to: ['admin@...'] } }

API Reference

NestJS

import { VitalsPulseModule, NestPulseModule } from 'vitals-node';

// in AppModule.imports:
VitalsPulseModule.forRoot(options)  // recommended
NestPulseModule.forRoot(options)    // alias

Express

import { expressPulse, createExpressRouter } from 'vitals-node/express';

app.use('/health', expressPulse(options));       // middleware
app.use('/health', createExpressRouter(options)); // router variant

Bun

import { bunPulse, withBunPulse } from 'vitals-node/bun';

const pulse = bunPulse(options);
// pulse.handler(req) — use in Bun.serve fetch

Bun.serve(withBunPulse({ ...options, fetch(req) { return new Response('OK'); } }));

Core (Standalone)

import { createMonitor, getMonitor } from 'vitals-node/core';

const monitor = createMonitor(options);
await monitor.start();

// Metrics
const metrics  = await monitor.getMetrics();
const health   = await monitor.getHealth();
const detailed = await monitor.getDetailedHealth();

// Routes
monitor.discoverRoutes(app, 'express');
monitor.registerRoute({ method: 'GET', path: '/api/orders' });
monitor.annotateRoute('POST', '/api/orders', schema);
await monitor.analyzeRouteSchemas(schemaAnalysisConfig);
const registry = monitor.getRoutes();

// Registry push
const payload = monitor.buildRegistryPayload({ groupId, projectId, projectPath, tags, baseUrl });
await monitor.pushRegistryTo({ url: webhook, groupId, projectId, projectPath, tags, apiKey, baseUrl });

// Agent tools
monitor.registerAgentTool(tool);
monitor.registerAgentTools(tools);
monitor.getAgentToolSchemas();
await monitor.invokeAgentTool(name, input);
monitor.enableCallApi('http://localhost:3000');  // register call_api built-in tool

monitor.stop();

Requirements

  • Node.js 18+ or Bun 1.0+
  • TypeScript 5+ (recommended)

License

MIT