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

apptvty

v0.3.2

Published

Server-side analytics and AEO (Agent Experience Optimization) SDK for websites. Logs agentic traffic, exposes a structured query endpoint for AI agents, and serves relevant ads on agent queries.

Readme

apptvty

AI traffic analytics and Agent Experience Optimization (AEO) for Node.js websites.

Apptvty makes your website visible to, and queryable by, AI agents — the crawlers and language models (GPTBot, ClaudeBot, Perplexity, etc.) that are increasingly the primary consumers of web content.

  • Detects and classifies AI traffic that standard analytics tools miss entirely
  • Exposes a /query endpoint so AI agents can get structured, sourced answers from your site
  • Earns USDC for your site when sponsored ads are served in query responses
  • Two lines to integrate into an existing Next.js or Express app
npm install apptvty

Node.js >= 18 required.


Quick start

Next.js (App Router)

Step 1 — Log all traffic

// middleware.ts
import { withApptvty } from 'apptvty/nextjs';

export default withApptvty({
  apiKey: process.env.APPTVTY_API_KEY!,
  siteId: process.env.APPTVTY_SITE_ID!,
});

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Step 2 — Expose the query endpoint

// app/query/route.ts
import { createNextjsQueryHandler } from 'apptvty/nextjs';

export const GET = createNextjsQueryHandler({
  apiKey: process.env.APPTVTY_API_KEY!,
  siteId: process.env.APPTVTY_SITE_ID!,
});

That's it. Every request is now logged, AI crawlers are classified, and AI agents can query your site at https://yoursite.com/query?q=your+question.


Express

import express from 'express';
import { createExpressMiddleware, createExpressQueryHandler } from 'apptvty/express';

const app = express();
const config = {
  apiKey: process.env.APPTVTY_API_KEY!,
  siteId: process.env.APPTVTY_SITE_ID!,
};

// Log all requests
app.use(createExpressMiddleware(config));

// Expose the query endpoint
app.get('/query', createExpressQueryHandler(config));

CLI setup (humans and agents)

The fastest way to get credentials and scaffold files:

# Interactive (human)
npx apptvty init

# Non-interactive (agent / CI) — prints JSON to stdout
npx apptvty init --domain mysite.com --framework nextjs --non-interactive

Trigger a re-crawl after publishing new content:

# Reindex your site (reads APPTVTY_SITE_ID and APPTVTY_API_KEY from .env.local / .env)
npx apptvty migrate

CLI commands:

| Command | Description | |---------|-------------| | init | Register site and scaffold integration files (default) | | migrate | Trigger immediate re-crawl/reindex of your site |

CLI flags:

| Flag | Description | |------|-------------| | --domain | Your site's domain, e.g. mysite.com (init only) | | --framework | nextjs, express, or other (init only, auto-detected) | | --non-interactive | No prompts; JSON output for scripting/agents | | --api-url | Override API base URL (staging / self-hosted) |

Agent-mode JSON output:

{
  "site_id": "site_abc123",
  "api_key": "ak_live_...",
  "company_id": "co_xyz...",
  "wallet_address": "0x...",
  "dashboard_url": "https://dashboard.apptvty.com/claim?token=...",
  "env_file": ".env.local",
  "env_vars": {
    "APPTVTY_SITE_ID": "site_abc123",
    "APPTVTY_API_KEY": "ak_live_..."
  }
}

The CLI writes credentials to .env.local (Next.js) or .env (Express/other) and scaffolds middleware.ts and app/query/route.ts if they do not already exist.


Package exports

| Import | Contents | |--------|----------| | apptvty | Core client, logger, crawler detector, query handler (framework-agnostic) | | apptvty/nextjs | Next.js App Router middleware and route handler | | apptvty/express | Express/Connect middleware and route handler | | apptvty/setup | register() and migrate() for programmatic setup and reindex |


Configuration

All middleware and handler functions accept an ApptvtyConfig object:

interface ApptvtyConfig {
  apiKey: string;          // Required. Your site's API key (ak_...)
  siteId: string;          // Required. Your site ID from the dashboard
  baseUrl?: string;        // Optional. Uses APPTVTY_API_URL env if set.
  batchSize?: number;      // Default: 50  — logs per flush
  flushInterval?: number;  // Default: 5000 — ms between auto-flushes
  debug?: boolean;         // Default: false — log errors to console
  queryPath?: string;      // Default: '/query' — path of AEO endpoint
}

Set these from environment variables so credentials are never in source code:

APPTVTY_API_KEY=ak_live_...
APPTVTY_SITE_ID=site_...
APPTVTY_API_URL=https://api.apptvty.com   # Optional. Default is api.apptvty.com; override for self-hosted only.

See .env.example for a copy-paste template.

Production: The SDK uses https://api.apptvty.com by default. You do not need to set APPTVTY_API_URL for production.

Using the SDK with a self-hosted backend

Only if you run apptvty-backend yourself (e.g. Serverless in your AWS account), point the SDK at that API:

  1. Deploy the backend (from the backend repo):
    cd apptvty-backend
    npx sls deploy --stage dev
  2. Get the API URL from the stack output:
    npx sls info --stage dev
    Use the ApiGatewayRestApiUrl (e.g. https://xxxx.execute-api.us-west-2.amazonaws.com/dev).
  3. Override the default (api.apptvty.com) in one of two ways:
    • Environment: Set APPTVTY_API_URL to that URL (no trailing slash).
    • In code: Pass baseUrl (or apiUrl for register/migrate).

The query endpoint

When an AI agent visits https://yoursite.com/query, the SDK responds with a self-describing discovery page so the agent knows how to use it:

{
  "version": "1.0",
  "endpoint": "https://yoursite.com/query",
  "description": "Query this site's content using natural language.",
  "usage": {
    "method": "GET",
    "parameters": {
      "q": { "type": "string", "required": true, "description": "Natural language question" },
      "lang": { "type": "string", "required": false, "description": "Preferred language (ISO 639-1)" }
    },
    "example": "https://yoursite.com/query?q=What+does+this+site+do"
  },
  "capabilities": ["rag", "sponsored_ads"],
  "rate_limit": "60 requests per minute"
}

When called with ?q=<question>:

{
  "success": true,
  "version": "1.0",
  "query": "What does this site do?",
  "answer": "Apptvty is an AI traffic analytics platform ...",
  "sources": [
    {
      "url": "https://yoursite.com/about",
      "title": "About Apptvty",
      "snippet": "Apptvty logs and classifies AI crawler traffic ...",
      "relevance": 0.94
    }
  ],
  "confidence": 0.91,
  "sponsored": {
    "label": "Sponsored",
    "text": "Get deeper AI analytics with AEO Pro — 14-day free trial",
    "url": "https://...",
    "advertiser": "AEO Pro",
    "impression_id": "imp_..."
  },
  "metadata": {
    "request_id": "req_...",
    "response_time_ms": 340,
    "tokens_used": 420,
    "site_id": "site_...",
    "timestamp": "2026-03-06T12:00:00.000Z"
  }
}

The sponsored field is present only when ads are enabled for your site and a matching ad exists. Ads are clearly labeled and serve as a revenue stream for site owners — no action required by the agent beyond passing the field through to the end user.

Error response:

{
  "success": false,
  "error": {
    "code": "QUERY_TOO_LONG",
    "message": "Query exceeds the 500-character limit.",
    "request_id": "req_...",
    "timestamp": "2026-03-06T12:00:00.000Z"
  }
}

Advertiser funding (X402)

If you are an agent or company paying for ads, Apptvty supports autonomous pre-paid credits via USDC on Base.

  1. Get the deposit address: Run npx apptvty init or check the platformDepositAddress field in the register() response.
  2. Send USDC: Transfer the desired amount of USDC (Base network) to that address.
  3. Sync your balance: Call the wallet sync endpoint with your transaction hash:
    curl "https://api.apptvty.com/v1/wallet/sync?tx_hash=0x..."
    Note: On-chain verification takes ~30-60 seconds.

Response headers always include:

  • Content-Type: application/json
  • Cache-Control: no-store
  • X-Robots-Tag: noindex

Analytics for coding agents

The SDK exposes methods so coding agents (e.g. Cursor, Claude Code) can fetch logs, activity, and errors without human intervention — similar to how aws logs tail works for AWS CLI.

import { ApptvtyClient } from 'apptvty';

const client = new ApptvtyClient({
  apiKey: process.env.APPTVTY_API_KEY!,
  siteId: process.env.APPTVTY_SITE_ID!,
});

// Wallet balance
const wallet = await client.getSiteWallet();

// Ad Campaign Insights (for advertisers)
const insights = await client.getCampaignInsights('camp_123');
console.log(`Top site: ${insights.top_publishers[0].domain}`);

| Method | Returns | |--------|---------| | getSiteStats() | 30-day overview (requests, AI %, crawlers, queries) | | getSiteDailyStats(days?) | Per-day breakdown | | getSiteActivity(limit?) | Recent requests (path, crawler, status) | | getSiteQueries(limit?) | Recent agent queries | | getSiteCrawlers(days?) | Crawler type breakdown | | getSiteWallet() | Balance, earnings, spend | | getCampaignInsights(id) | Ad performance (impressions per site, daily spend) |


Local logs dashboard

Deploy a localized analytics portal on your own domain using the framework-agnostic dashboard handler.

Next.js (App Router)

// app/api/apptvty/logs/route.ts
import { createNextjsDashboardHandler } from 'apptvty/nextjs';

export const GET = createNextjsDashboardHandler({
  apiKey: process.env.APPTVTY_API_KEY!,
  siteId: process.env.APPTVTY_SITE_ID!,
});

Your dashboard is now available at https://yoursite.com/api/apptvty/logs.


Crawler detection

The SDK identifies 50+ AI crawlers by user-agent string. Access detection directly:

import { detectCrawler, getKnownCrawlerNames } from 'apptvty';

const info = detectCrawler('GPTBot/1.1');
// {
//   isAi: true,
//   name: 'GPTBot',
//   organization: 'OpenAI',
//   confidence: 0.95,
//   detectionMethod: 'exact_match'
// }

const names = getKnownCrawlerNames();
// ['GPTBot', 'ClaudeBot', 'PerplexityBot', 'BingBot', ...]

CrawlerInfo fields:

| Field | Type | Description | |-------|------|-------------| | isAi | boolean | Whether the traffic is from an AI/LLM system | | name | string \| null | Crawler name, e.g. "GPTBot" | | organization | string \| null | Organization, e.g. "OpenAI" | | confidence | number | Detection certainty, 0.0 – 1.0 | | detectionMethod | string | exact_match / pattern_match / heuristic / none |

Crawlers recognized (partial list): GPTBot, OpenAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-Web, Anthropic-AI, Google-Extended, GoogleOther, Bingbot, PerplexityBot, YouBot, DuckDuckBot, Meta-ExternalAgent, LinkedInBot, AppleBot, TwitterBot, CohereAI, AI2Bot, MistralBot.


Programmatic registration

For agents and scripts that need to register a site without the CLI:

import { register, RegistrationError } from 'apptvty/setup';

try {
  const result = await register({
    domain: 'mysite.com',
    framework: 'nextjs',   // 'nextjs' | 'express' | 'other'
    agentId: 'my-agent',   // optional — analytics on which agent registered
  });

  console.log(result.apiKey);        // 'ak_live_...'
  console.log(result.siteId);        // 'site_...'
  console.log(result.walletAddress); // '0x...' or null
  console.log(result.dashboardUrl);  // Dashboard claim link (valid 30 days)
  console.log(result.setup.envVars); // { APPTVTY_SITE_ID, APPTVTY_API_KEY }

} catch (err) {
  if (err instanceof RegistrationError) {
    // err.code: 'DOMAIN_TAKEN' | 'REGISTRATION_FAILED' | ...
    console.error(err.code, err.message);
  }
}

RegisterOptions:

| Field | Type | Required | Description | |-------|------|----------|-------------| | domain | string | Yes | Site domain, e.g. mysite.com | | framework | string | No | nextjs, express, or other | | agentId | string | No | Identifies the registering agent (for analytics) | | apiUrl | string | No | Override API base URL for staging/self-hosted |

RegisterResult:

| Field | Type | Description | |-------|------|-------------| | siteId | string | Site identifier | | apiKey | string | API key for SDK config | | companyId | string | Company identifier | | walletAddress | string \| null | Crossmint wallet for USDC earnings | | dashboardUrl | string | Dashboard claim link (valid 30 days) | | claimTokenExpiresAt | string | ISO timestamp of link expiry | | setup.envVars | object | Env vars to write to your .env file | | setup.files | object \| undefined | Optional scaffold file contents |

Registration is idempotent per domain. Registering the same domain twice throws RegistrationError with code: 'DOMAIN_TAKEN'.

Programmatic migrate (reindex)

Trigger an immediate re-crawl after publishing new content:

import { migrate, MigrateError } from 'apptvty/setup';

try {
  const result = await migrate({
    siteId: process.env.APPTVTY_SITE_ID!,
    apiKey: process.env.APPTVTY_API_KEY!,
    apiUrl: 'https://api.apptvty.com',  // optional — for staging/self-hosted
  });
  console.log(result.message); // "Reindex started. Your site content will be updated within a few minutes."
} catch (err) {
  if (err instanceof MigrateError) {
    console.error(err.code, err.message);
  }
}

The crawl runs asynchronously; the endpoint returns 202 immediately.


Advanced: framework-agnostic usage

Use ApptvtyClient and RequestLogger directly for custom frameworks:

import { ApptvtyClient, RequestLogger, detectCrawler, createQueryHandler } from 'apptvty';

const config = { apiKey: 'ak_...', siteId: 'site_...' };
const client = new ApptvtyClient(config);
const logger = new RequestLogger(client, config);

// In your request handler:
const crawlerInfo = detectCrawler(req.headers['user-agent'] ?? '');
logger.enqueue({
  site_id: config.siteId,
  timestamp: new Date().toISOString(),
  method: req.method,
  path: req.url,
  status_code: res.statusCode,
  response_time_ms: elapsedMs,
  ip_address: req.socket.remoteAddress ?? '',
  user_agent: req.headers['user-agent'] ?? '',
  referer: req.headers.referer ?? null,
  is_ai_crawler: crawlerInfo.isAi,
  crawler_type: crawlerInfo.name,
  crawler_organization: crawlerInfo.organization,
  confidence_score: crawlerInfo.confidence,
});

// Flush on shutdown
process.on('SIGTERM', () => logger.flush());

Request logging behavior

  • Logs are batched in memory and flushed every flushInterval ms (default 5s) or when batchSize entries accumulate (default 50)
  • Logging is non-blocking — failures never propagate to the request handler
  • The logger automatically flushes on SIGTERM, SIGINT, and beforeExit
  • The flush timer is unreferenced — it will not keep a process alive after all other work is done

Paths skipped by middleware (never logged):

  • /_next/ — Next.js build assets
  • /api/_* — Internal Next.js API routes
  • /favicon.ico
  • Static file extensions: .svg, .png, .jpg, .jpeg, .gif, .webp, .ico, .woff, .woff2, .ttf, .css, .js.map

Analytics dashboard

Visit your dashboardUrl after registration to:

  • View AI vs human traffic split over time
  • See which crawlers are visiting and how often
  • Browse agent queries and the answers served
  • Track USDC earnings from sponsored ads
  • Manage API keys and site settings

The link is valid for 30 days. Add an email address in the dashboard to establish a permanent login.


Testing

npm test           # Unit + integration (hits real dev API)
npm run test:unit  # Unit tests only (mocked API)
npm run test:integration  # Integration tests against deployed dev API

Integration tests use https://api.apptvty.com by default: they register a new test site, send real logs, and verify analytics and migrate. No env vars required. Needs network access.


Publishing the SDK (npm)

The SDK is an npm package. “Deploy” means publishing to the registry so users can npm install apptvty.

Prerequisites

Release steps

  1. Bump version in package.json (or use npm version):

    npm version patch   # 0.1.0 → 0.1.1
    # or: npm version minor   # 0.1.0 → 0.2.0
  2. Run tests (optional but recommended):

    npm run test:unit
    npm run test:integration   # uses api.apptvty.com
  3. Publish:

    npm publish

    prepublishOnly runs npm run build && npm run typecheck automatically before the package is uploaded.

For a scoped package (e.g. @apptvty/sdk), make sure package.json has "name": "@apptvty/sdk" and use:

npm publish --access public

License

MIT