apptvty 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/apptvty.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/cli.js');
@@ -4,7 +4,7 @@ import {
4
4
  createQueryHandler,
5
5
  detectCrawler,
6
6
  getClientIp
7
- } from "./chunk-WATTAPBA.mjs";
7
+ } from "./chunk-JVOMOEEL.mjs";
8
8
 
9
9
  // src/middleware/express.ts
10
10
  var instances = /* @__PURE__ */ new Map();
@@ -84,4 +84,4 @@ export {
84
84
  createExpressMiddleware,
85
85
  createExpressQueryHandler
86
86
  };
87
- //# sourceMappingURL=chunk-RGUS6IL6.mjs.map
87
+ //# sourceMappingURL=chunk-3ITWIW4P.mjs.map
@@ -183,13 +183,20 @@ var RequestLogger = class {
183
183
  this.timer = setInterval(() => {
184
184
  void this.flush();
185
185
  }, interval);
186
- if (this.timer.unref) this.timer.unref();
187
- const handleExit = () => {
188
- void this.flushSync();
189
- };
190
- process.once("SIGTERM", handleExit);
191
- process.once("SIGINT", handleExit);
192
- process.once("beforeExit", handleExit);
186
+ if (this.timer && typeof this.timer.unref === "function") {
187
+ this.timer.unref();
188
+ }
189
+ if (typeof process !== "undefined" && typeof process.once === "function") {
190
+ const handleExit = () => {
191
+ void this.flushSync();
192
+ };
193
+ try {
194
+ process.once("SIGTERM", handleExit);
195
+ process.once("SIGINT", handleExit);
196
+ process.once("beforeExit", handleExit);
197
+ } catch {
198
+ }
199
+ }
193
200
  }
194
201
  /** Enqueue a single log entry. Non-blocking. */
195
202
  enqueue(entry) {
@@ -499,4 +506,4 @@ export {
499
506
  getKnownCrawlerNames,
500
507
  createQueryHandler
501
508
  };
502
- //# sourceMappingURL=chunk-WATTAPBA.mjs.map
509
+ //# sourceMappingURL=chunk-JVOMOEEL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/logger.ts","../src/crawler.ts","../src/query-handler.ts"],"sourcesContent":["/**\n * HTTP client for the Apptvty API.\n *\n * Handles:\n * - Batch log ingestion (/v1/logs/batch)\n * - Query processing (/v1/query) — returns answer + optional ad\n * - Impression logging (/v1/impressions) — triggers billing for ads\n */\n\nimport type {\n ApptvtyConfig,\n BackendQueryResponse,\n CrawlerBreakdown,\n DailyStat,\n ImpressionLog,\n PageAdsResponse,\n QueryRequest,\n RecentActivityItem,\n RecentQueryItem,\n RequestLogEntry,\n SiteOverviewStats,\n SiteWalletInfo,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.apptvty.com';\n\nfunction resolveBaseUrl(config: ApptvtyConfig): string {\n const raw: string =\n config.baseUrl ??\n (typeof process !== 'undefined' ? process.env?.APPTVTY_API_URL : undefined) ??\n DEFAULT_BASE_URL;\n return raw.replace(/\\/$/, '');\n}\n\nexport class ApptvtyClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly siteId: string;\n private readonly debug: boolean;\n\n constructor(config: ApptvtyConfig) {\n this.baseUrl = resolveBaseUrl(config);\n this.apiKey = config.apiKey;\n this.siteId = config.siteId;\n this.debug = config.debug ?? false;\n }\n\n /**\n * Send a batch of request log entries to the Apptvty ingestion API.\n * Called by the logger's auto-flush — not called directly by user code.\n */\n async sendLogs(logs: RequestLogEntry[]): Promise<void> {\n if (logs.length === 0) return;\n\n try {\n await this.post('/v1/logs/batch', { logs });\n this.log(`Flushed ${logs.length} log(s)`);\n } catch (err) {\n if (err instanceof ApptvtyTrialExpiredError) {\n // Always visible — coding agents and developers need to see this\n console.warn(\n `\\n[apptvty] ⚠ FREE TRIAL EXPIRED\\n` +\n `[apptvty] Log in and upgrade to keep receiving agent analytics.\\n` +\n `[apptvty] Dashboard: ${err.dashboardUrl}\\n`,\n );\n return;\n }\n // Other logging failures must never propagate to the user's request path\n this.warn('Failed to send logs:', err);\n }\n }\n\n /**\n * Send a query to the Apptvty backend.\n * The backend runs RAG against the site's indexed content and,\n * if ads are enabled for the site, attaches a relevant sponsored ad.\n *\n * @throws on non-retryable errors (network errors, 5xx) — callers should catch\n */\n async query(req: QueryRequest): Promise<BackendQueryResponse> {\n // ApptvtyTrialExpiredError propagates to the query handler, which surfaces it to the agent\n const response = await this.post<BackendQueryResponse>('/v1/query', req);\n return response;\n }\n\n /**\n * Get relevant ads for a page (for HTML injection when crawler is detected).\n * Used by middleware to inject ads into HTML responses.\n */\n async getAdsForPage(req: { site_id: string; page_path: string }): Promise<PageAdsResponse> {\n const response = await this.post<PageAdsResponse>('/v1/ads/for-page', req);\n return response;\n }\n\n /**\n * Log an ad impression back to Apptvty.\n *\n * Called after returning a query response that contains a `sponsored` block.\n * This is what triggers the billing cycle:\n * - Advertiser gets charged (debited from their USDC ad budget)\n * - Publisher (the website) gets credited in USDC\n *\n * This call is fire-and-forget. The SDK logs a warning on failure\n * but does not throw — a missed impression log is better than\n * breaking the query response.\n */\n async logImpression(impression: ImpressionLog): Promise<void> {\n try {\n await this.post('/v1/impressions', impression);\n this.log(`Impression logged: ${impression.impression_id}`);\n } catch (err) {\n this.warn('Failed to log impression (billing may be delayed):', err);\n }\n }\n\n // ─── Analytics (for coding agents) ───────────────────────────────────────────\n // These allow agents to check activity, logs, and errors without a human.\n\n /** Get 30-day traffic overview (requests, AI %, crawlers, queries). */\n async getSiteStats(): Promise<SiteOverviewStats> {\n return this.get<SiteOverviewStats>(`/v1/sites/${this.siteId}/stats`);\n }\n\n /** Get day-by-day stats (default 30 days, max 90). */\n async getSiteDailyStats(days = 30): Promise<{ days: number; stats: DailyStat[] }> {\n return this.get(`/v1/sites/${this.siteId}/stats/daily`, { days: String(days) });\n }\n\n /** Get crawler breakdown by type (default 30 days). */\n async getSiteCrawlers(days = 30): Promise<{ days: number; crawlers: CrawlerBreakdown[] }> {\n return this.get(`/v1/sites/${this.siteId}/crawlers`, { days: String(days) });\n }\n\n /** Get recent activity feed (last hour, default 50 items, max 200). */\n async getSiteActivity(limit = 50): Promise<{ activity: RecentActivityItem[] }> {\n return this.get(`/v1/sites/${this.siteId}/activity`, { limit: String(limit) });\n }\n\n /** Get recent agent queries (default 50, max 200). */\n async getSiteQueries(limit = 50): Promise<{ queries: RecentQueryItem[] }> {\n return this.get(`/v1/sites/${this.siteId}/queries`, { limit: String(limit) });\n }\n\n /** Get wallet balance and earnings. */\n async getSiteWallet(): Promise<SiteWalletInfo> {\n return this.get<SiteWalletInfo>(`/v1/sites/${this.siteId}/wallet`);\n }\n\n private async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));\n }\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'User-Agent': 'apptvty-sdk/0.1.0',\n },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new ApptvtyApiError(response.status, path, text);\n }\n return response.json() as Promise<T>;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'apptvty-sdk/0.1.0',\n },\n body: JSON.stringify(body),\n // Node 18+ fetch: set a reasonable timeout via AbortSignal\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n if (response.status === 402) {\n let dashboardUrl = 'https://dashboard.apptvty.com/login';\n try {\n const json = JSON.parse(text);\n dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;\n } catch {}\n throw new ApptvtyTrialExpiredError(dashboardUrl);\n }\n throw new ApptvtyApiError(response.status, path, text);\n }\n\n return response.json() as Promise<T>;\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[apptvty]', ...args);\n }\n\n private warn(...args: unknown[]): void {\n if (this.debug) console.warn('[apptvty]', ...args);\n }\n}\n\nexport class ApptvtyApiError extends Error {\n constructor(\n public readonly statusCode: number,\n public readonly path: string,\n public readonly body: string,\n ) {\n super(`Apptvty API error ${statusCode} at ${path}: ${body}`);\n this.name = 'ApptvtyApiError';\n }\n}\n\nexport class ApptvtyTrialExpiredError extends Error {\n constructor(public readonly dashboardUrl: string) {\n super(`Apptvty free trial has expired. Log in to continue: ${dashboardUrl}`);\n this.name = 'ApptvtyTrialExpiredError';\n }\n}\n","/**\n * Batched request logger.\n *\n * Queues RequestLogEntry objects in memory and flushes them in batches\n * to the Apptvty API. This keeps per-request overhead near zero.\n *\n * Flush triggers:\n * 1. Queue reaches batchSize (default 50)\n * 2. flushInterval elapses (default 5s)\n * 3. process.exit / SIGTERM (best-effort sync flush)\n */\n\nimport type { ApptvtyClient } from './client.js';\nimport type { ApptvtyConfig, RequestLogEntry } from './types.js';\n\nexport class RequestLogger {\n private queue: RequestLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private flushing = false;\n private readonly batchSize: number;\n private readonly debug: boolean;\n\n constructor(\n private readonly client: ApptvtyClient,\n config: ApptvtyConfig,\n ) {\n this.batchSize = config.batchSize ?? 50;\n this.debug = config.debug ?? false;\n\n const interval = config.flushInterval ?? 5_000;\n this.timer = setInterval(() => { void this.flush(); }, interval);\n\n // Unref so the timer doesn't keep the process alive\n if (this.timer && typeof (this.timer as any).unref === 'function') {\n (this.timer as any).unref();\n }\n\n // Best-effort flush on process shutdown (Node-only)\n if (typeof process !== 'undefined' && typeof process.once === 'function') {\n const handleExit = () => { void this.flushSync(); };\n try {\n process.once('SIGTERM', handleExit);\n process.once('SIGINT', handleExit);\n process.once('beforeExit', handleExit);\n } catch {\n // Ignore errors in environments where process exists but signal listeners don't\n }\n }\n }\n\n /** Enqueue a single log entry. Non-blocking. */\n enqueue(entry: RequestLogEntry): void {\n this.queue.push(entry);\n if (this.queue.length >= this.batchSize) {\n // Don't await — fire and forget\n void this.flush();\n }\n }\n\n /** Flush the current queue to the API. */\n async flush(): Promise<void> {\n if (this.flushing || this.queue.length === 0) return;\n\n this.flushing = true;\n const batch = this.queue.splice(0, this.batchSize);\n\n try {\n await this.client.sendLogs(batch);\n } catch {\n // Already handled (logged) inside client.sendLogs — nothing to do here\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Synchronous-ish flush for process shutdown.\n * Fires the fetch and doesn't await to avoid blocking exit handlers.\n */\n private flushSync(): void {\n if (this.queue.length === 0) return;\n const batch = this.queue.splice(0);\n // Fire without await — best effort on exit\n void this.client.sendLogs(batch);\n }\n\n /** Stop the interval timer. Call when you want to fully tear down the SDK. */\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n void this.flush();\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[apptvty:logger]', ...args);\n }\n}\n\n// ─── Helpers used by middleware to build log entries ──────────────────────────\n\nexport function getClientIp(headers: Record<string, string | string[] | undefined>): string {\n const forwarded = headers['x-forwarded-for'];\n if (forwarded) {\n const first = Array.isArray(forwarded) ? forwarded[0] : forwarded;\n return first.split(',')[0].trim();\n }\n return (headers['x-real-ip'] as string) ?? 'unknown';\n}\n","/**\n * Lightweight AI crawler detection.\n *\n * This is the single source of truth for crawler classification in the SDK.\n * The backend (Python analytics API and TypeScript handlers) should eventually\n * consume this same list rather than maintaining separate copies.\n */\n\nimport type { CrawlerInfo } from './types.js';\n\ninterface KnownCrawler {\n name: string;\n organization: string;\n patterns: RegExp[];\n}\n\nconst KNOWN_CRAWLERS: KnownCrawler[] = [\n // OpenAI\n { name: 'GPTBot', organization: 'OpenAI', patterns: [/GPTBot/i, /ChatGPT-User/i] },\n { name: 'OpenAI-SearchBot', organization: 'OpenAI', patterns: [/OpenAI-SearchBot/i] },\n // Anthropic\n { name: 'ClaudeBot', organization: 'Anthropic', patterns: [/ClaudeBot/i, /Claude-Web/i, /Anthropic-AI/i] },\n // Google\n { name: 'Google-Extended', organization: 'Google AI', patterns: [/Google-Extended/i] },\n { name: 'GoogleOther', organization: 'Google AI', patterns: [/GoogleOther/i] },\n { name: 'Googlebot', organization: 'Google', patterns: [/Googlebot/i] },\n // Microsoft\n { name: 'Bingbot', organization: 'Microsoft', patterns: [/bingbot/i, /BingPreview/i] },\n // Perplexity\n { name: 'PerplexityBot', organization: 'Perplexity', patterns: [/PerplexityBot/i] },\n // You.com\n { name: 'YouBot', organization: 'You.com', patterns: [/YouBot/i] },\n // Meta\n { name: 'Meta-ExternalAgent', organization: 'Meta', patterns: [/Meta-ExternalAgent/i] },\n { name: 'FacebookBot', organization: 'Meta', patterns: [/facebookexternalhit/i, /FacebookBot/i] },\n // Apple\n { name: 'AppleBot', organization: 'Apple', patterns: [/Applebot/i] },\n // Twitter/X\n { name: 'TwitterBot', organization: 'Twitter/X', patterns: [/Twitterbot/i] },\n // LinkedIn\n { name: 'LinkedInBot', organization: 'LinkedIn', patterns: [/LinkedInBot/i] },\n // DuckDuckGo\n { name: 'DuckDuckBot', organization: 'DuckDuckGo', patterns: [/DuckDuckBot/i] },\n // Cohere\n { name: 'Cohere-AI', organization: 'Cohere', patterns: [/Cohere-AI/i, /cohere-ai/i] },\n // Allen Institute\n { name: 'AI2Bot', organization: 'Allen Institute for AI', patterns: [/AI2Bot/i] },\n // Mistral\n { name: 'MistralBot', organization: 'Mistral AI', patterns: [/MistralBot/i] },\n];\n\n/**\n * Patterns that reliably indicate AI/LLM-related traffic.\n * Each entry is [pattern, confidence].\n */\nconst AI_PATTERN_MATCHES: [RegExp, number][] = [\n [/openai/i, 0.90],\n [/anthropic/i, 0.90],\n [/gpt|chatgpt/i, 0.85],\n [/claude/i, 0.85],\n [/perplexity/i, 0.85],\n [/llm|language[- ]model/i, 0.75],\n [/bot.*ai|ai.*bot/i, 0.75],\n [/search.*ai|ai.*search/i, 0.70],\n];\n\n/**\n * Patterns that strongly suggest a human browser.\n * We check these before generic bot scoring to reduce false positives.\n * NOTE: We deliberately exclude Chrome/Safari/Firefox from this list because\n * many bots spoof those strings. We only short-circuit on strings that bots\n * have little reason to include.\n */\nconst HUMAN_SIGNALS: RegExp[] = [\n /Mozilla\\/5\\.0.*\\(Windows NT.*\\) AppleWebKit.*Chrome.*Safari/i,\n /Mozilla\\/5\\.0.*\\(Macintosh.*\\) AppleWebKit.*Version.*Safari/i,\n];\n\nexport function detectCrawler(userAgent: string): CrawlerInfo {\n if (!userAgent || userAgent.length < 4) {\n return { isAi: false, name: null, organization: null, confidence: 0.3, detectionMethod: 'heuristic' };\n }\n\n // 1. Exact match against known crawlers — highest confidence\n for (const crawler of KNOWN_CRAWLERS) {\n for (const pattern of crawler.patterns) {\n if (pattern.test(userAgent)) {\n return {\n isAi: true,\n name: crawler.name,\n organization: crawler.organization,\n confidence: 0.95,\n detectionMethod: 'exact_match',\n };\n }\n }\n }\n\n // 2. Short-circuit for strong human browser signals\n for (const pattern of HUMAN_SIGNALS) {\n if (pattern.test(userAgent)) {\n return { isAi: false, name: null, organization: null, confidence: 0.85, detectionMethod: 'heuristic' };\n }\n }\n\n // 3. Pattern-based AI detection\n for (const [pattern, confidence] of AI_PATTERN_MATCHES) {\n if (pattern.test(userAgent)) {\n return {\n isAi: true,\n name: extractBotName(userAgent),\n organization: null,\n confidence,\n detectionMethod: 'pattern_match',\n };\n }\n }\n\n // 4. Generic heuristic scoring for unknown bots\n let score = 0;\n if (/bot|crawler|spider|scraper/i.test(userAgent)) score += 0.4;\n if (/python-requests|curl\\/|wget\\/|scrapy|go-http-client/i.test(userAgent)) score += 0.3;\n if (!userAgent.includes('Mozilla')) score += 0.2;\n if (userAgent.length < 20) score += 0.2;\n\n if (score >= 0.5) {\n return {\n isAi: false, // Generic bot — not classified as AI\n name: 'unknown_bot',\n organization: null,\n confidence: Math.min(score, 0.8),\n detectionMethod: 'heuristic',\n };\n }\n\n return { isAi: false, name: null, organization: null, confidence: 0.1, detectionMethod: 'none' };\n}\n\nfunction extractBotName(userAgent: string): string {\n const parts = userAgent.split(/[\\s/;(]+/);\n for (const part of parts) {\n if (/bot|agent|crawler|ai/i.test(part) && part.length > 2) {\n return part.replace(/[^a-zA-Z0-9-_]/g, '');\n }\n }\n return 'unknown_ai_bot';\n}\n\n/** Returns the list of all known crawlers for reference (e.g. for agents.txt generation) */\nexport function getKnownCrawlerNames(): string[] {\n return KNOWN_CRAWLERS.map(c => c.name);\n}\n","/**\n * Framework-agnostic query endpoint handler.\n *\n * Serves the site's AEO (Agent Experience Optimization) query page.\n *\n * GET /query → Returns a self-describing discovery JSON\n * GET /query?q=<question> → Returns an AI-generated answer from the site's\n * indexed content, plus a sponsored ad if ads are\n * enabled and a matching ad exists.\n *\n * When an ad is included in the response, this handler fires an async\n * impression log back to Apptvty to trigger billing:\n * - Advertiser is charged (debited from their USDC ad budget)\n * - Publisher earns USDC (credited to their Crossmint wallet)\n */\n\nimport type { ApptvtyClient } from './client.js';\nimport { ApptvtyTrialExpiredError } from './client.js';\nimport type { ApptvtyConfig } from './types.js';\nimport type {\n AgentErrorResponse,\n AgentQueryResponse,\n QueryEndpointDiscovery,\n} from './types.js';\n\nexport interface QueryHandlerRequest {\n query: string | null; // ?q= param, null if not present\n lang: string | null; // ?lang= param\n surface_ads?: boolean; // ?surface_ads=1|0 — include ads (default true)\n ai_crawler?: boolean; // ?ai_crawler=1 — \"I'm an agent crawling, surface ads\"\n userAgent: string;\n ipAddress: string;\n /** Full URL of the request, used to build the example in the discovery response */\n requestUrl: string;\n}\n\nexport interface QueryHandlerResponse {\n status: number;\n body: AgentQueryResponse | QueryEndpointDiscovery | AgentErrorResponse;\n headers: Record<string, string>;\n}\n\nconst RESPONSE_HEADERS: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-store',\n 'X-Robots-Tag': 'noindex', // Don't index the query endpoint itself\n};\n\nexport function createQueryHandler(client: ApptvtyClient, config: ApptvtyConfig) {\n const queryPath = config.queryPath ?? '/query';\n\n return async function handleQuery(req: QueryHandlerRequest): Promise<QueryHandlerResponse> {\n // ── Discovery response (no ?q= param) ──────────────────────────────────\n if (!req.query || req.query.trim() === '') {\n const origin = getOrigin(req.requestUrl);\n const discovery: QueryEndpointDiscovery = {\n version: '1.0',\n endpoint: `${origin}${queryPath}`,\n description:\n 'Query this site\\'s content directly. Submit a question and receive a structured JSON ' +\n 'response with an AI-generated answer, source citations, and confidence score. ' +\n 'Ideal for AI agents, search engines, and automated systems seeking direct answers.',\n crawl_policy:\n 'Do not crawl this entire site unnecessarily. Use this query endpoint with ?q=your_question to get the relevant information you need. Full-site crawling is discouraged.',\n usage: {\n method: 'GET',\n parameters: {\n q: {\n type: 'string',\n required: true,\n description: 'Your question or search query (max 500 characters)',\n },\n lang: {\n type: 'string',\n required: false,\n description: 'Preferred response language (ISO 639-1 code, default: en)',\n },\n surface_ads: {\n type: 'boolean',\n required: false,\n description: 'Include sponsored ads in response (1 = yes, default; 0 = no). When crawling, use ai_crawler=1 to signal you want ads.',\n },\n ai_crawler: {\n type: 'boolean',\n required: false,\n description: 'Set to 1 when you are an AI agent/crawler. Signals the server to surface ads. Use with ?q=... for targeted answers + ads.',\n },\n },\n example: `${origin}${queryPath}?q=what+features+do+you+offer&ai_crawler=1`,\n response_format: 'application/json',\n },\n capabilities: [\n 'Natural language question answering',\n 'Source citations with relevance scores',\n 'Structured JSON for easy parsing',\n 'Supports follow-up context via session continuity',\n 'Query-preferred: use ?q= instead of full-site crawl',\n ],\n rate_limit: '100 requests per hour per IP',\n };\n\n return { status: 200, body: discovery, headers: RESPONSE_HEADERS };\n }\n\n // ── Validate query ──────────────────────────────────────────────────────\n const trimmedQuery = req.query.trim();\n if (trimmedQuery.length > 500) {\n return errorResponse(400, 'QUERY_TOO_LONG', 'Query must be 500 characters or fewer');\n }\n\n // ── Call Apptvty backend (RAG + optional ad) ────────────────────────────\n const requestId = crypto.randomUUID();\n const timestamp = new Date().toISOString();\n const startMs = Date.now();\n\n const surfaceAds = req.surface_ads !== false; // default true\n const aiCrawler = req.ai_crawler === true;\n\n let backendResponse;\n try {\n backendResponse = await client.query({\n site_id: config.siteId,\n query: trimmedQuery,\n agent_ua: req.userAgent,\n agent_ip: req.ipAddress,\n request_id: requestId,\n timestamp,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n });\n } catch (err) {\n if (err instanceof ApptvtyTrialExpiredError) {\n return errorResponse(\n 402,\n 'TRIAL_EXPIRED',\n `Apptvty free trial has expired. The site owner must log in and upgrade to continue. Dashboard: ${err.dashboardUrl}`,\n );\n }\n return errorResponse(502, 'UPSTREAM_ERROR', 'Could not retrieve an answer at this time');\n }\n\n const responseTimeMs = Date.now() - startMs;\n\n // ── Log impression if an ad was served ─────────────────────────────────\n //\n // This is fire-and-forget. We do NOT await it — the agent gets its\n // response immediately, and the billing event is recorded asynchronously.\n //\n // Billing flow triggered by these calls (one per ad):\n // 1. Apptvty records impression_id + metadata\n // 2. Advertiser's USDC ad budget is debited\n // 3. Publisher's Crossmint wallet is credited\n const ads = backendResponse.sponsored\n ? (Array.isArray(backendResponse.sponsored) ? backendResponse.sponsored : [backendResponse.sponsored])\n : [];\n for (const ad of ads) {\n void client.logImpression({\n impression_id: ad.impression_id,\n site_id: config.siteId,\n query: trimmedQuery,\n agent_ua: req.userAgent,\n agent_ip: req.ipAddress,\n timestamp,\n });\n }\n\n // ── Build agent response ────────────────────────────────────────────────\n const agentResponse: AgentQueryResponse = {\n success: true,\n version: '1.0',\n query: trimmedQuery,\n answer: backendResponse.answer,\n sources: backendResponse.sources,\n confidence: backendResponse.confidence,\n ...(backendResponse.sponsored && { sponsored: backendResponse.sponsored }),\n metadata: {\n request_id: requestId,\n response_time_ms: responseTimeMs,\n tokens_used: backendResponse.tokens_used,\n site_id: config.siteId,\n timestamp,\n },\n };\n\n return { status: 200, body: agentResponse, headers: RESPONSE_HEADERS };\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction errorResponse(\n status: number,\n code: string,\n message: string,\n): QueryHandlerResponse {\n const body: AgentErrorResponse = {\n success: false,\n error: {\n code,\n message,\n request_id: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n },\n };\n return { status, body, headers: RESPONSE_HEADERS };\n}\n\nfunction getOrigin(url: string): string {\n try {\n const parsed = new URL(url);\n return parsed.origin;\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  createQueryHandler,
5
5
  detectCrawler,
6
6
  getClientIp
7
- } from "./chunk-WATTAPBA.mjs";
7
+ } from "./chunk-JVOMOEEL.mjs";
8
8
 
9
9
  // src/middleware/nextjs.ts
10
10
  import { NextResponse } from "next/server";
@@ -147,4 +147,4 @@ export {
147
147
  withApptvty,
148
148
  createNextjsQueryHandler
149
149
  };
150
- //# sourceMappingURL=chunk-XOWRKLFM.mjs.map
150
+ //# sourceMappingURL=chunk-OZAZWZNO.mjs.map
package/dist/cli.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
 
4
3
  // src/cli.ts
@@ -136,18 +135,41 @@ function writeFile(relativePath, content, overwrite = false) {
136
135
  }
137
136
  function appendEnvFile(path, vars) {
138
137
  const fullPath = (0, import_path.join)(process.cwd(), path);
139
- const lines = Object.entries(vars).map(([k, v]) => `${k}=${v}`).join("\n");
140
- let existing = "";
138
+ let content = "";
141
139
  try {
142
- existing = (0, import_fs.readFileSync)(fullPath, "utf-8");
140
+ content = (0, import_fs.readFileSync)(fullPath, "utf-8");
143
141
  } catch {
144
142
  }
145
- const toAdd = Object.entries(vars).filter(([k]) => !existing.includes(k)).map(([k, v]) => `${k}=${v}`).join("\n");
146
- if (!toAdd) return;
147
- (0, import_fs.writeFileSync)(fullPath, existing ? `${existing}
148
- ${toAdd}
149
- ` : `${toAdd}
150
- `, "utf-8");
143
+ const lines = content.split("\n");
144
+ const outLines = [...lines];
145
+ const keysToAppend = { ...vars };
146
+ for (let i = 0; i < outLines.length; i++) {
147
+ const line = outLines[i];
148
+ const m = line.match(/^\s*(?:#\s*)?([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/);
149
+ if (m) {
150
+ const key = m[1];
151
+ const value = m[2].trim();
152
+ if (keysToAppend[key]) {
153
+ const isPlaceholder = !value || value.startsWith("your_") || value.startsWith("<");
154
+ const isCommented = line.trim().startsWith("#");
155
+ if (isCommented || isPlaceholder) {
156
+ outLines[i] = `${key}=${keysToAppend[key]}`;
157
+ delete keysToAppend[key];
158
+ } else {
159
+ delete keysToAppend[key];
160
+ }
161
+ }
162
+ }
163
+ }
164
+ const remaining = Object.entries(keysToAppend);
165
+ if (remaining.length > 0) {
166
+ if (outLines.length > 0 && outLines[outLines.length - 1].trim() !== "") {
167
+ outLines.push("");
168
+ }
169
+ remaining.forEach(([k, v]) => outLines.push(`${k}=${v}`));
170
+ outLines.push("");
171
+ }
172
+ (0, import_fs.writeFileSync)(fullPath, outLines.join("\n"), "utf-8");
151
173
  }
152
174
  function loadEnvVars() {
153
175
  const envFiles = [".env.local", ".env"];
package/dist/index.js CHANGED
@@ -218,13 +218,20 @@ var RequestLogger = class {
218
218
  this.timer = setInterval(() => {
219
219
  void this.flush();
220
220
  }, interval);
221
- if (this.timer.unref) this.timer.unref();
222
- const handleExit = () => {
223
- void this.flushSync();
224
- };
225
- process.once("SIGTERM", handleExit);
226
- process.once("SIGINT", handleExit);
227
- process.once("beforeExit", handleExit);
221
+ if (this.timer && typeof this.timer.unref === "function") {
222
+ this.timer.unref();
223
+ }
224
+ if (typeof process !== "undefined" && typeof process.once === "function") {
225
+ const handleExit = () => {
226
+ void this.flushSync();
227
+ };
228
+ try {
229
+ process.once("SIGTERM", handleExit);
230
+ process.once("SIGINT", handleExit);
231
+ process.once("beforeExit", handleExit);
232
+ } catch {
233
+ }
234
+ }
228
235
  }
229
236
  /** Enqueue a single log entry. Non-blocking. */
230
237
  enqueue(entry) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/logger.ts","../src/crawler.ts","../src/query-handler.ts","../src/middleware/nextjs.ts","../src/middleware/express.ts"],"sourcesContent":["/**\n * apptvty — Server-side analytics and AEO SDK\n *\n * Quickstart (Next.js App Router):\n *\n * // middleware.ts\n * import { withApptvty } from 'apptvty/nextjs';\n * export default withApptvty({ apiKey: 'ak_...', siteId: 'site_...' });\n * export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };\n *\n * // app/query/route.ts ← your AEO query page\n * import { createNextjsQueryHandler } from 'apptvty/nextjs';\n * export const GET = createNextjsQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' });\n *\n * Quickstart (Express):\n *\n * import express from 'express';\n * import { createExpressMiddleware, createExpressQueryHandler } from 'apptvty/express';\n *\n * const app = express();\n * const config = { apiKey: 'ak_...', siteId: 'site_...' };\n * app.use(createExpressMiddleware(config));\n * app.get('/query', createExpressQueryHandler(config));\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\nexport type {\n ApptvtyConfig,\n AgentQueryResponse,\n AgentErrorResponse,\n QueryEndpointDiscovery,\n QuerySource,\n SponsoredAd,\n PageAd,\n PageAdsResponse,\n RequestLogEntry,\n CrawlerInfo,\n ImpressionLog,\n SiteOverviewStats,\n DailyStat,\n CrawlerBreakdown,\n RecentActivityItem,\n RecentQueryItem,\n SiteWalletInfo,\n} from './types.js';\n\n// ─── Core utilities (framework-agnostic) ──────────────────────────────────────\nexport { ApptvtyClient, ApptvtyApiError } from './client.js';\nexport { RequestLogger } from './logger.js';\nexport { detectCrawler, getKnownCrawlerNames } from './crawler.js';\nexport { createQueryHandler } from './query-handler.js';\n\n// ─── Next.js integration ──────────────────────────────────────────────────────\nexport { withApptvty, createNextjsQueryHandler } from './middleware/nextjs.js';\n\n// ─── Express integration ──────────────────────────────────────────────────────\nexport { createExpressMiddleware, createExpressQueryHandler } from './middleware/express.js';\n","/**\n * HTTP client for the Apptvty API.\n *\n * Handles:\n * - Batch log ingestion (/v1/logs/batch)\n * - Query processing (/v1/query) — returns answer + optional ad\n * - Impression logging (/v1/impressions) — triggers billing for ads\n */\n\nimport type {\n ApptvtyConfig,\n BackendQueryResponse,\n CrawlerBreakdown,\n DailyStat,\n ImpressionLog,\n PageAdsResponse,\n QueryRequest,\n RecentActivityItem,\n RecentQueryItem,\n RequestLogEntry,\n SiteOverviewStats,\n SiteWalletInfo,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.apptvty.com';\n\nfunction resolveBaseUrl(config: ApptvtyConfig): string {\n const raw: string =\n config.baseUrl ??\n (typeof process !== 'undefined' ? process.env?.APPTVTY_API_URL : undefined) ??\n DEFAULT_BASE_URL;\n return raw.replace(/\\/$/, '');\n}\n\nexport class ApptvtyClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly siteId: string;\n private readonly debug: boolean;\n\n constructor(config: ApptvtyConfig) {\n this.baseUrl = resolveBaseUrl(config);\n this.apiKey = config.apiKey;\n this.siteId = config.siteId;\n this.debug = config.debug ?? false;\n }\n\n /**\n * Send a batch of request log entries to the Apptvty ingestion API.\n * Called by the logger's auto-flush — not called directly by user code.\n */\n async sendLogs(logs: RequestLogEntry[]): Promise<void> {\n if (logs.length === 0) return;\n\n try {\n await this.post('/v1/logs/batch', { logs });\n this.log(`Flushed ${logs.length} log(s)`);\n } catch (err) {\n if (err instanceof ApptvtyTrialExpiredError) {\n // Always visible — coding agents and developers need to see this\n console.warn(\n `\\n[apptvty] ⚠ FREE TRIAL EXPIRED\\n` +\n `[apptvty] Log in and upgrade to keep receiving agent analytics.\\n` +\n `[apptvty] Dashboard: ${err.dashboardUrl}\\n`,\n );\n return;\n }\n // Other logging failures must never propagate to the user's request path\n this.warn('Failed to send logs:', err);\n }\n }\n\n /**\n * Send a query to the Apptvty backend.\n * The backend runs RAG against the site's indexed content and,\n * if ads are enabled for the site, attaches a relevant sponsored ad.\n *\n * @throws on non-retryable errors (network errors, 5xx) — callers should catch\n */\n async query(req: QueryRequest): Promise<BackendQueryResponse> {\n // ApptvtyTrialExpiredError propagates to the query handler, which surfaces it to the agent\n const response = await this.post<BackendQueryResponse>('/v1/query', req);\n return response;\n }\n\n /**\n * Get relevant ads for a page (for HTML injection when crawler is detected).\n * Used by middleware to inject ads into HTML responses.\n */\n async getAdsForPage(req: { site_id: string; page_path: string }): Promise<PageAdsResponse> {\n const response = await this.post<PageAdsResponse>('/v1/ads/for-page', req);\n return response;\n }\n\n /**\n * Log an ad impression back to Apptvty.\n *\n * Called after returning a query response that contains a `sponsored` block.\n * This is what triggers the billing cycle:\n * - Advertiser gets charged (debited from their USDC ad budget)\n * - Publisher (the website) gets credited in USDC\n *\n * This call is fire-and-forget. The SDK logs a warning on failure\n * but does not throw — a missed impression log is better than\n * breaking the query response.\n */\n async logImpression(impression: ImpressionLog): Promise<void> {\n try {\n await this.post('/v1/impressions', impression);\n this.log(`Impression logged: ${impression.impression_id}`);\n } catch (err) {\n this.warn('Failed to log impression (billing may be delayed):', err);\n }\n }\n\n // ─── Analytics (for coding agents) ───────────────────────────────────────────\n // These allow agents to check activity, logs, and errors without a human.\n\n /** Get 30-day traffic overview (requests, AI %, crawlers, queries). */\n async getSiteStats(): Promise<SiteOverviewStats> {\n return this.get<SiteOverviewStats>(`/v1/sites/${this.siteId}/stats`);\n }\n\n /** Get day-by-day stats (default 30 days, max 90). */\n async getSiteDailyStats(days = 30): Promise<{ days: number; stats: DailyStat[] }> {\n return this.get(`/v1/sites/${this.siteId}/stats/daily`, { days: String(days) });\n }\n\n /** Get crawler breakdown by type (default 30 days). */\n async getSiteCrawlers(days = 30): Promise<{ days: number; crawlers: CrawlerBreakdown[] }> {\n return this.get(`/v1/sites/${this.siteId}/crawlers`, { days: String(days) });\n }\n\n /** Get recent activity feed (last hour, default 50 items, max 200). */\n async getSiteActivity(limit = 50): Promise<{ activity: RecentActivityItem[] }> {\n return this.get(`/v1/sites/${this.siteId}/activity`, { limit: String(limit) });\n }\n\n /** Get recent agent queries (default 50, max 200). */\n async getSiteQueries(limit = 50): Promise<{ queries: RecentQueryItem[] }> {\n return this.get(`/v1/sites/${this.siteId}/queries`, { limit: String(limit) });\n }\n\n /** Get wallet balance and earnings. */\n async getSiteWallet(): Promise<SiteWalletInfo> {\n return this.get<SiteWalletInfo>(`/v1/sites/${this.siteId}/wallet`);\n }\n\n private async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));\n }\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'User-Agent': 'apptvty-sdk/0.1.0',\n },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new ApptvtyApiError(response.status, path, text);\n }\n return response.json() as Promise<T>;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'apptvty-sdk/0.1.0',\n },\n body: JSON.stringify(body),\n // Node 18+ fetch: set a reasonable timeout via AbortSignal\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n if (response.status === 402) {\n let dashboardUrl = 'https://dashboard.apptvty.com/login';\n try {\n const json = JSON.parse(text);\n dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;\n } catch {}\n throw new ApptvtyTrialExpiredError(dashboardUrl);\n }\n throw new ApptvtyApiError(response.status, path, text);\n }\n\n return response.json() as Promise<T>;\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[apptvty]', ...args);\n }\n\n private warn(...args: unknown[]): void {\n if (this.debug) console.warn('[apptvty]', ...args);\n }\n}\n\nexport class ApptvtyApiError extends Error {\n constructor(\n public readonly statusCode: number,\n public readonly path: string,\n public readonly body: string,\n ) {\n super(`Apptvty API error ${statusCode} at ${path}: ${body}`);\n this.name = 'ApptvtyApiError';\n }\n}\n\nexport class ApptvtyTrialExpiredError extends Error {\n constructor(public readonly dashboardUrl: string) {\n super(`Apptvty free trial has expired. Log in to continue: ${dashboardUrl}`);\n this.name = 'ApptvtyTrialExpiredError';\n }\n}\n","/**\n * Batched request logger.\n *\n * Queues RequestLogEntry objects in memory and flushes them in batches\n * to the Apptvty API. This keeps per-request overhead near zero.\n *\n * Flush triggers:\n * 1. Queue reaches batchSize (default 50)\n * 2. flushInterval elapses (default 5s)\n * 3. process.exit / SIGTERM (best-effort sync flush)\n */\n\nimport type { ApptvtyClient } from './client.js';\nimport type { ApptvtyConfig, RequestLogEntry } from './types.js';\n\nexport class RequestLogger {\n private queue: RequestLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private flushing = false;\n private readonly batchSize: number;\n private readonly debug: boolean;\n\n constructor(\n private readonly client: ApptvtyClient,\n config: ApptvtyConfig,\n ) {\n this.batchSize = config.batchSize ?? 50;\n this.debug = config.debug ?? false;\n\n const interval = config.flushInterval ?? 5_000;\n this.timer = setInterval(() => { void this.flush(); }, interval);\n\n // Unref so the timer doesn't keep the process alive\n if (this.timer.unref) this.timer.unref();\n\n // Best-effort flush on process shutdown\n const handleExit = () => { void this.flushSync(); };\n process.once('SIGTERM', handleExit);\n process.once('SIGINT', handleExit);\n process.once('beforeExit', handleExit);\n }\n\n /** Enqueue a single log entry. Non-blocking. */\n enqueue(entry: RequestLogEntry): void {\n this.queue.push(entry);\n if (this.queue.length >= this.batchSize) {\n // Don't await — fire and forget\n void this.flush();\n }\n }\n\n /** Flush the current queue to the API. */\n async flush(): Promise<void> {\n if (this.flushing || this.queue.length === 0) return;\n\n this.flushing = true;\n const batch = this.queue.splice(0, this.batchSize);\n\n try {\n await this.client.sendLogs(batch);\n } catch {\n // Already handled (logged) inside client.sendLogs — nothing to do here\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Synchronous-ish flush for process shutdown.\n * Fires the fetch and doesn't await to avoid blocking exit handlers.\n */\n private flushSync(): void {\n if (this.queue.length === 0) return;\n const batch = this.queue.splice(0);\n // Fire without await — best effort on exit\n void this.client.sendLogs(batch);\n }\n\n /** Stop the interval timer. Call when you want to fully tear down the SDK. */\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n void this.flush();\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[apptvty:logger]', ...args);\n }\n}\n\n// ─── Helpers used by middleware to build log entries ──────────────────────────\n\nexport function getClientIp(headers: Record<string, string | string[] | undefined>): string {\n const forwarded = headers['x-forwarded-for'];\n if (forwarded) {\n const first = Array.isArray(forwarded) ? forwarded[0] : forwarded;\n return first.split(',')[0].trim();\n }\n return (headers['x-real-ip'] as string) ?? 'unknown';\n}\n","/**\n * Lightweight AI crawler detection.\n *\n * This is the single source of truth for crawler classification in the SDK.\n * The backend (Python analytics API and TypeScript handlers) should eventually\n * consume this same list rather than maintaining separate copies.\n */\n\nimport type { CrawlerInfo } from './types.js';\n\ninterface KnownCrawler {\n name: string;\n organization: string;\n patterns: RegExp[];\n}\n\nconst KNOWN_CRAWLERS: KnownCrawler[] = [\n // OpenAI\n { name: 'GPTBot', organization: 'OpenAI', patterns: [/GPTBot/i, /ChatGPT-User/i] },\n { name: 'OpenAI-SearchBot', organization: 'OpenAI', patterns: [/OpenAI-SearchBot/i] },\n // Anthropic\n { name: 'ClaudeBot', organization: 'Anthropic', patterns: [/ClaudeBot/i, /Claude-Web/i, /Anthropic-AI/i] },\n // Google\n { name: 'Google-Extended', organization: 'Google AI', patterns: [/Google-Extended/i] },\n { name: 'GoogleOther', organization: 'Google AI', patterns: [/GoogleOther/i] },\n { name: 'Googlebot', organization: 'Google', patterns: [/Googlebot/i] },\n // Microsoft\n { name: 'Bingbot', organization: 'Microsoft', patterns: [/bingbot/i, /BingPreview/i] },\n // Perplexity\n { name: 'PerplexityBot', organization: 'Perplexity', patterns: [/PerplexityBot/i] },\n // You.com\n { name: 'YouBot', organization: 'You.com', patterns: [/YouBot/i] },\n // Meta\n { name: 'Meta-ExternalAgent', organization: 'Meta', patterns: [/Meta-ExternalAgent/i] },\n { name: 'FacebookBot', organization: 'Meta', patterns: [/facebookexternalhit/i, /FacebookBot/i] },\n // Apple\n { name: 'AppleBot', organization: 'Apple', patterns: [/Applebot/i] },\n // Twitter/X\n { name: 'TwitterBot', organization: 'Twitter/X', patterns: [/Twitterbot/i] },\n // LinkedIn\n { name: 'LinkedInBot', organization: 'LinkedIn', patterns: [/LinkedInBot/i] },\n // DuckDuckGo\n { name: 'DuckDuckBot', organization: 'DuckDuckGo', patterns: [/DuckDuckBot/i] },\n // Cohere\n { name: 'Cohere-AI', organization: 'Cohere', patterns: [/Cohere-AI/i, /cohere-ai/i] },\n // Allen Institute\n { name: 'AI2Bot', organization: 'Allen Institute for AI', patterns: [/AI2Bot/i] },\n // Mistral\n { name: 'MistralBot', organization: 'Mistral AI', patterns: [/MistralBot/i] },\n];\n\n/**\n * Patterns that reliably indicate AI/LLM-related traffic.\n * Each entry is [pattern, confidence].\n */\nconst AI_PATTERN_MATCHES: [RegExp, number][] = [\n [/openai/i, 0.90],\n [/anthropic/i, 0.90],\n [/gpt|chatgpt/i, 0.85],\n [/claude/i, 0.85],\n [/perplexity/i, 0.85],\n [/llm|language[- ]model/i, 0.75],\n [/bot.*ai|ai.*bot/i, 0.75],\n [/search.*ai|ai.*search/i, 0.70],\n];\n\n/**\n * Patterns that strongly suggest a human browser.\n * We check these before generic bot scoring to reduce false positives.\n * NOTE: We deliberately exclude Chrome/Safari/Firefox from this list because\n * many bots spoof those strings. We only short-circuit on strings that bots\n * have little reason to include.\n */\nconst HUMAN_SIGNALS: RegExp[] = [\n /Mozilla\\/5\\.0.*\\(Windows NT.*\\) AppleWebKit.*Chrome.*Safari/i,\n /Mozilla\\/5\\.0.*\\(Macintosh.*\\) AppleWebKit.*Version.*Safari/i,\n];\n\nexport function detectCrawler(userAgent: string): CrawlerInfo {\n if (!userAgent || userAgent.length < 4) {\n return { isAi: false, name: null, organization: null, confidence: 0.3, detectionMethod: 'heuristic' };\n }\n\n // 1. Exact match against known crawlers — highest confidence\n for (const crawler of KNOWN_CRAWLERS) {\n for (const pattern of crawler.patterns) {\n if (pattern.test(userAgent)) {\n return {\n isAi: true,\n name: crawler.name,\n organization: crawler.organization,\n confidence: 0.95,\n detectionMethod: 'exact_match',\n };\n }\n }\n }\n\n // 2. Short-circuit for strong human browser signals\n for (const pattern of HUMAN_SIGNALS) {\n if (pattern.test(userAgent)) {\n return { isAi: false, name: null, organization: null, confidence: 0.85, detectionMethod: 'heuristic' };\n }\n }\n\n // 3. Pattern-based AI detection\n for (const [pattern, confidence] of AI_PATTERN_MATCHES) {\n if (pattern.test(userAgent)) {\n return {\n isAi: true,\n name: extractBotName(userAgent),\n organization: null,\n confidence,\n detectionMethod: 'pattern_match',\n };\n }\n }\n\n // 4. Generic heuristic scoring for unknown bots\n let score = 0;\n if (/bot|crawler|spider|scraper/i.test(userAgent)) score += 0.4;\n if (/python-requests|curl\\/|wget\\/|scrapy|go-http-client/i.test(userAgent)) score += 0.3;\n if (!userAgent.includes('Mozilla')) score += 0.2;\n if (userAgent.length < 20) score += 0.2;\n\n if (score >= 0.5) {\n return {\n isAi: false, // Generic bot — not classified as AI\n name: 'unknown_bot',\n organization: null,\n confidence: Math.min(score, 0.8),\n detectionMethod: 'heuristic',\n };\n }\n\n return { isAi: false, name: null, organization: null, confidence: 0.1, detectionMethod: 'none' };\n}\n\nfunction extractBotName(userAgent: string): string {\n const parts = userAgent.split(/[\\s/;(]+/);\n for (const part of parts) {\n if (/bot|agent|crawler|ai/i.test(part) && part.length > 2) {\n return part.replace(/[^a-zA-Z0-9-_]/g, '');\n }\n }\n return 'unknown_ai_bot';\n}\n\n/** Returns the list of all known crawlers for reference (e.g. for agents.txt generation) */\nexport function getKnownCrawlerNames(): string[] {\n return KNOWN_CRAWLERS.map(c => c.name);\n}\n","/**\n * Framework-agnostic query endpoint handler.\n *\n * Serves the site's AEO (Agent Experience Optimization) query page.\n *\n * GET /query → Returns a self-describing discovery JSON\n * GET /query?q=<question> → Returns an AI-generated answer from the site's\n * indexed content, plus a sponsored ad if ads are\n * enabled and a matching ad exists.\n *\n * When an ad is included in the response, this handler fires an async\n * impression log back to Apptvty to trigger billing:\n * - Advertiser is charged (debited from their USDC ad budget)\n * - Publisher earns USDC (credited to their Crossmint wallet)\n */\n\nimport type { ApptvtyClient } from './client.js';\nimport { ApptvtyTrialExpiredError } from './client.js';\nimport type { ApptvtyConfig } from './types.js';\nimport type {\n AgentErrorResponse,\n AgentQueryResponse,\n QueryEndpointDiscovery,\n} from './types.js';\n\nexport interface QueryHandlerRequest {\n query: string | null; // ?q= param, null if not present\n lang: string | null; // ?lang= param\n surface_ads?: boolean; // ?surface_ads=1|0 — include ads (default true)\n ai_crawler?: boolean; // ?ai_crawler=1 — \"I'm an agent crawling, surface ads\"\n userAgent: string;\n ipAddress: string;\n /** Full URL of the request, used to build the example in the discovery response */\n requestUrl: string;\n}\n\nexport interface QueryHandlerResponse {\n status: number;\n body: AgentQueryResponse | QueryEndpointDiscovery | AgentErrorResponse;\n headers: Record<string, string>;\n}\n\nconst RESPONSE_HEADERS: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-store',\n 'X-Robots-Tag': 'noindex', // Don't index the query endpoint itself\n};\n\nexport function createQueryHandler(client: ApptvtyClient, config: ApptvtyConfig) {\n const queryPath = config.queryPath ?? '/query';\n\n return async function handleQuery(req: QueryHandlerRequest): Promise<QueryHandlerResponse> {\n // ── Discovery response (no ?q= param) ──────────────────────────────────\n if (!req.query || req.query.trim() === '') {\n const origin = getOrigin(req.requestUrl);\n const discovery: QueryEndpointDiscovery = {\n version: '1.0',\n endpoint: `${origin}${queryPath}`,\n description:\n 'Query this site\\'s content directly. Submit a question and receive a structured JSON ' +\n 'response with an AI-generated answer, source citations, and confidence score. ' +\n 'Ideal for AI agents, search engines, and automated systems seeking direct answers.',\n crawl_policy:\n 'Do not crawl this entire site unnecessarily. Use this query endpoint with ?q=your_question to get the relevant information you need. Full-site crawling is discouraged.',\n usage: {\n method: 'GET',\n parameters: {\n q: {\n type: 'string',\n required: true,\n description: 'Your question or search query (max 500 characters)',\n },\n lang: {\n type: 'string',\n required: false,\n description: 'Preferred response language (ISO 639-1 code, default: en)',\n },\n surface_ads: {\n type: 'boolean',\n required: false,\n description: 'Include sponsored ads in response (1 = yes, default; 0 = no). When crawling, use ai_crawler=1 to signal you want ads.',\n },\n ai_crawler: {\n type: 'boolean',\n required: false,\n description: 'Set to 1 when you are an AI agent/crawler. Signals the server to surface ads. Use with ?q=... for targeted answers + ads.',\n },\n },\n example: `${origin}${queryPath}?q=what+features+do+you+offer&ai_crawler=1`,\n response_format: 'application/json',\n },\n capabilities: [\n 'Natural language question answering',\n 'Source citations with relevance scores',\n 'Structured JSON for easy parsing',\n 'Supports follow-up context via session continuity',\n 'Query-preferred: use ?q= instead of full-site crawl',\n ],\n rate_limit: '100 requests per hour per IP',\n };\n\n return { status: 200, body: discovery, headers: RESPONSE_HEADERS };\n }\n\n // ── Validate query ──────────────────────────────────────────────────────\n const trimmedQuery = req.query.trim();\n if (trimmedQuery.length > 500) {\n return errorResponse(400, 'QUERY_TOO_LONG', 'Query must be 500 characters or fewer');\n }\n\n // ── Call Apptvty backend (RAG + optional ad) ────────────────────────────\n const requestId = crypto.randomUUID();\n const timestamp = new Date().toISOString();\n const startMs = Date.now();\n\n const surfaceAds = req.surface_ads !== false; // default true\n const aiCrawler = req.ai_crawler === true;\n\n let backendResponse;\n try {\n backendResponse = await client.query({\n site_id: config.siteId,\n query: trimmedQuery,\n agent_ua: req.userAgent,\n agent_ip: req.ipAddress,\n request_id: requestId,\n timestamp,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n });\n } catch (err) {\n if (err instanceof ApptvtyTrialExpiredError) {\n return errorResponse(\n 402,\n 'TRIAL_EXPIRED',\n `Apptvty free trial has expired. The site owner must log in and upgrade to continue. Dashboard: ${err.dashboardUrl}`,\n );\n }\n return errorResponse(502, 'UPSTREAM_ERROR', 'Could not retrieve an answer at this time');\n }\n\n const responseTimeMs = Date.now() - startMs;\n\n // ── Log impression if an ad was served ─────────────────────────────────\n //\n // This is fire-and-forget. We do NOT await it — the agent gets its\n // response immediately, and the billing event is recorded asynchronously.\n //\n // Billing flow triggered by these calls (one per ad):\n // 1. Apptvty records impression_id + metadata\n // 2. Advertiser's USDC ad budget is debited\n // 3. Publisher's Crossmint wallet is credited\n const ads = backendResponse.sponsored\n ? (Array.isArray(backendResponse.sponsored) ? backendResponse.sponsored : [backendResponse.sponsored])\n : [];\n for (const ad of ads) {\n void client.logImpression({\n impression_id: ad.impression_id,\n site_id: config.siteId,\n query: trimmedQuery,\n agent_ua: req.userAgent,\n agent_ip: req.ipAddress,\n timestamp,\n });\n }\n\n // ── Build agent response ────────────────────────────────────────────────\n const agentResponse: AgentQueryResponse = {\n success: true,\n version: '1.0',\n query: trimmedQuery,\n answer: backendResponse.answer,\n sources: backendResponse.sources,\n confidence: backendResponse.confidence,\n ...(backendResponse.sponsored && { sponsored: backendResponse.sponsored }),\n metadata: {\n request_id: requestId,\n response_time_ms: responseTimeMs,\n tokens_used: backendResponse.tokens_used,\n site_id: config.siteId,\n timestamp,\n },\n };\n\n return { status: 200, body: agentResponse, headers: RESPONSE_HEADERS };\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction errorResponse(\n status: number,\n code: string,\n message: string,\n): QueryHandlerResponse {\n const body: AgentErrorResponse = {\n success: false,\n error: {\n code,\n message,\n request_id: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n },\n };\n return { status, body, headers: RESPONSE_HEADERS };\n}\n\nfunction getOrigin(url: string): string {\n try {\n const parsed = new URL(url);\n return parsed.origin;\n } catch {\n return '';\n }\n}\n","/**\n * Next.js App Router integration for the Apptvty SDK.\n *\n * Usage — two pieces:\n *\n * 1. Wrap your existing middleware.ts (or use ours standalone):\n *\n * import { withApptvty } from 'apptvty/nextjs';\n * export default withApptvty({ apiKey: 'ak_...', siteId: 'site_...' });\n * // or wrap your existing middleware:\n * export default withApptvty(config, yourMiddleware);\n *\n * 2. Create app/query/route.ts for the AEO query endpoint:\n *\n * import { createNextjsQueryHandler } from 'apptvty/nextjs';\n * export const GET = createNextjsQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' });\n *\n * The SDK logs all requests (especially agentic traffic) to Apptvty\n * and serves structured AI answers + optional sponsored ads on /query.\n */\n\nimport type { NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { ApptvtyClient } from '../client.js';\nimport { detectCrawler } from '../crawler.js';\nimport { RequestLogger, getClientIp } from '../logger.js';\nimport { createQueryHandler } from '../query-handler.js';\nimport type { ApptvtyConfig, RequestLogEntry } from '../types.js';\n\n/** Convert Next request headers to a plain object (Web API Headers.entries() at runtime). */\nfunction headersToRecord(h: NextRequest['headers']): Record<string, string> {\n const entries = (h as unknown as { entries(): IterableIterator<[string, string]> }).entries();\n return Object.fromEntries(Array.from(entries));\n}\n\n// ─── Shared singleton instances per config (keyed by apiKey) ─────────────────\n\nconst instances = new Map<string, { client: ApptvtyClient; logger: RequestLogger }>();\n\nfunction getInstance(config: ApptvtyConfig) {\n const key = config.apiKey;\n if (!instances.has(key)) {\n const client = new ApptvtyClient(config);\n const logger = new RequestLogger(client, config);\n instances.set(key, { client, logger });\n }\n return instances.get(key)!;\n}\n\n// ─── Middleware wrapper ───────────────────────────────────────────────────────\n\ntype NextMiddleware = (request: NextRequest) => Response | NextResponse | Promise<Response | NextResponse>;\n\n/**\n * Wraps a Next.js middleware function (or creates a passthrough) with\n * Apptvty request logging. All requests passing through middleware are\n * logged to Apptvty, with AI crawlers automatically classified.\n *\n * @example\n * // middleware.ts\n * import { withApptvty } from 'apptvty/nextjs';\n * export default withApptvty({ apiKey: 'ak_...', siteId: 'site_...' });\n */\nexport function withApptvty(\n config: ApptvtyConfig,\n next?: NextMiddleware,\n): NextMiddleware {\n const { client, logger } = getInstance(config);\n const queryPath = config.queryPath ?? '/query';\n\n return async function apptvtyMiddleware(request: NextRequest): Promise<NextResponse> {\n const startMs = Date.now();\n const userAgent = request.headers.get('user-agent') ?? '';\n const crawlerInfo = detectCrawler(userAgent);\n const aiCrawlerParam = parseBoolParam(request.nextUrl.searchParams.get('ai_crawler'), false);\n const isCrawler = crawlerInfo.isAi || aiCrawlerParam;\n\n // Run the user's middleware (or passthrough)\n let response: Response | NextResponse;\n try {\n response = next ? await next(request) : NextResponse.next();\n } catch (err) {\n // Don't swallow application errors\n throw err;\n }\n\n const responseTimeMs = Date.now() - startMs;\n\n // Skip logging for Next.js internals and static assets\n const { pathname } = request.nextUrl;\n if (shouldSkip(pathname)) {\n return response as NextResponse;\n }\n\n const headers = headersToRecord(request.headers);\n const entry: RequestLogEntry = {\n site_id: config.siteId,\n timestamp: new Date().toISOString(),\n method: request.method,\n path: pathname,\n status_code: response.status,\n response_time_ms: responseTimeMs,\n ip_address: getClientIp(headers),\n user_agent: userAgent,\n referer: request.headers.get('referer'),\n is_ai_crawler: crawlerInfo.isAi,\n crawler_type: crawlerInfo.name,\n crawler_organization: crawlerInfo.organization,\n confidence_score: crawlerInfo.confidence,\n };\n\n logger.enqueue(entry);\n\n // Ad injection: when crawler hits an HTML page, inject sponsored ads\n if (isCrawler && response.ok && !pathname.startsWith(queryPath)) {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('text/html')) {\n try {\n const modified = await injectAdsIntoHtml(response, client, config.siteId, pathname);\n if (modified) return modified;\n } catch (err) {\n // Never break the response for ad injection failures\n if (config.debug) console.warn('[apptvty] Ad injection failed:', err);\n }\n }\n }\n\n return response as NextResponse;\n };\n}\n\n// ─── Query route handler ──────────────────────────────────────────────────────\n\n/**\n * Creates a Next.js App Router GET handler for the AEO query endpoint.\n *\n * @example\n * // app/query/route.ts\n * import { createNextjsQueryHandler } from 'apptvty/nextjs';\n * export const GET = createNextjsQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' });\n */\nexport function createNextjsQueryHandler(config: ApptvtyConfig) {\n const { client } = getInstance(config);\n const handleQuery = createQueryHandler(client, config);\n\n return async function GET(request: NextRequest): Promise<NextResponse> {\n const { searchParams } = request.nextUrl;\n const q = searchParams.get('q');\n const lang = searchParams.get('lang');\n const surfaceAds = parseBoolParam(searchParams.get('surface_ads'), true);\n const aiCrawler = parseBoolParam(searchParams.get('ai_crawler'), false);\n const userAgent = request.headers.get('user-agent') ?? '';\n const headers = headersToRecord(request.headers);\n\n const result = await handleQuery({\n query: q,\n lang,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n userAgent,\n ipAddress: getClientIp(headers),\n requestUrl: request.url,\n });\n\n return NextResponse.json(result.body, {\n status: result.status,\n headers: result.headers,\n });\n };\n}\n\n// ─── HTML ad injection ────────────────────────────────────────────────────────\n\nconst AD_INJECTION_MARKER = '<!-- apptvty-sponsored -->';\n\nfunction buildAdBlock(ads: Array<{ text: string; url: string; advertiser: string }>): string {\n const items = ads\n .map(\n (ad) =>\n `<li><a href=\"${escapeHtml(ad.url)}\" rel=\"nofollow\">${escapeHtml(ad.text)}</a> <small>— ${escapeHtml(ad.advertiser)}</small></li>`\n )\n .join('\\n');\n return `\n<section aria-label=\"Sponsored\" data-sponsored ${AD_INJECTION_MARKER}>\n <h3>Sponsored</h3>\n <ul>${items}</ul>\n</section>`;\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nasync function injectAdsIntoHtml(\n response: Response,\n client: ApptvtyClient,\n siteId: string,\n pathname: string\n): Promise<NextResponse | null> {\n const html = await response.text();\n if (!html || html.includes(AD_INJECTION_MARKER)) return null;\n\n const pageAds = await client.getAdsForPage({ site_id: siteId, page_path: pathname });\n if (!pageAds.ads || pageAds.ads.length === 0) return null;\n\n const adBlock = buildAdBlock(pageAds.ads);\n let modified: string;\n\n if (html.includes('</body>')) {\n modified = html.replace('</body>', `${adBlock}\\n</body>`);\n } else if (html.includes('</html>')) {\n modified = html.replace('</html>', `${adBlock}\\n</html>`);\n } else {\n modified = html + adBlock;\n }\n\n return new NextResponse(modified, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction parseBoolParam(value: string | null, defaultValue: boolean): boolean {\n if (value === null) return defaultValue;\n return value === '1' || value === 'true' || value === 'yes';\n}\n\nfunction shouldSkip(pathname: string): boolean {\n return (\n pathname.startsWith('/_next/') ||\n pathname.startsWith('/api/_') ||\n pathname === '/favicon.ico' ||\n /\\.(svg|png|jpg|jpeg|gif|webp|ico|woff2?|ttf|css|js\\.map)$/.test(pathname)\n );\n}\n","/**\n * Express / Node.js integration for the Apptvty SDK.\n *\n * Usage:\n *\n * import express from 'express';\n * import { createExpressMiddleware, createExpressQueryHandler } from 'apptvty/express';\n *\n * const app = express();\n * const config = { apiKey: 'ak_...', siteId: 'site_...' };\n *\n * // 1. Log all traffic\n * app.use(createExpressMiddleware(config));\n *\n * // 2. Mount the AEO query page\n * app.get('/query', createExpressQueryHandler(config));\n *\n * Works with any Connect-compatible framework (Express, Fastify via @fastify/express, etc.)\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { ApptvtyClient } from '../client.js';\nimport { detectCrawler } from '../crawler.js';\nimport { RequestLogger, getClientIp } from '../logger.js';\nimport { createQueryHandler } from '../query-handler.js';\nimport type { ApptvtyConfig, RequestLogEntry } from '../types.js';\n\nexport type ConnectMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n) => void;\n\nexport type ConnectHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => void | Promise<void>;\n\n// ─── Shared singleton instances per config ────────────────────────────────────\n\nconst instances = new Map<string, { client: ApptvtyClient; logger: RequestLogger }>();\n\nfunction getInstance(config: ApptvtyConfig) {\n const key = config.apiKey;\n if (!instances.has(key)) {\n const client = new ApptvtyClient(config);\n const logger = new RequestLogger(client, config);\n instances.set(key, { client, logger });\n }\n return instances.get(key)!;\n}\n\n// ─── Traffic logging middleware ───────────────────────────────────────────────\n\n/**\n * Express middleware that logs every request to Apptvty.\n * Captures: method, path, status, response time, user-agent, IP, crawler classification.\n *\n * Mount this before your routes so all traffic is captured.\n *\n * @example\n * app.use(createExpressMiddleware({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressMiddleware(config: ApptvtyConfig): ConnectMiddleware {\n const { logger } = getInstance(config);\n\n return function apptvtyLogger(req, res, next) {\n const startMs = Date.now();\n const userAgent = req.headers['user-agent'] ?? '';\n const crawlerInfo = detectCrawler(userAgent);\n const path = req.url ?? '/';\n\n // Intercept response finish to capture status code and timing\n res.on('finish', () => {\n if (shouldSkip(path)) return;\n\n const entry: RequestLogEntry = {\n site_id: config.siteId,\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path,\n status_code: res.statusCode,\n response_time_ms: Date.now() - startMs,\n ip_address: getClientIp(req.headers as Record<string, string | string[] | undefined>),\n user_agent: userAgent,\n referer: (req.headers['referer'] as string) ?? null,\n is_ai_crawler: crawlerInfo.isAi,\n crawler_type: crawlerInfo.name,\n crawler_organization: crawlerInfo.organization,\n confidence_score: crawlerInfo.confidence,\n };\n\n logger.enqueue(entry);\n });\n\n next();\n };\n}\n\n// ─── Query endpoint handler ───────────────────────────────────────────────────\n\n/**\n * Express route handler for the AEO query endpoint.\n *\n * Mount this at the path configured in your dashboard (default: /query).\n *\n * @example\n * app.get('/query', createExpressQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressQueryHandler(config: ApptvtyConfig): ConnectHandler {\n const { client } = getInstance(config);\n const handleQuery = createQueryHandler(client, config);\n\n return async function queryHandler(req, res) {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const q = url.searchParams.get('q');\n const lang = url.searchParams.get('lang');\n const surfaceAds = parseBoolParam(url.searchParams.get('surface_ads'), true);\n const aiCrawler = parseBoolParam(url.searchParams.get('ai_crawler'), false);\n const userAgent = req.headers['user-agent'] ?? '';\n\n const result = await handleQuery({\n query: q,\n lang,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n userAgent,\n ipAddress: getClientIp(req.headers as Record<string, string | string[] | undefined>),\n requestUrl: url.toString(),\n });\n\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n res.statusCode = result.status;\n res.end(JSON.stringify(result.body));\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction parseBoolParam(value: string | null, defaultValue: boolean): boolean {\n if (value === null) return defaultValue;\n return value === '1' || value === 'true' || value === 'yes';\n}\n\nfunction shouldSkip(path: string): boolean {\n return (\n path.startsWith('/_next/') ||\n /\\.(svg|png|jpg|jpeg|gif|webp|ico|woff2?|ttf|css|js\\.map)$/.test(path)\n );\n}\n"],"mappings":"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","names":["instances","getInstance","shouldSkip","parseBoolParam"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/logger.ts","../src/crawler.ts","../src/query-handler.ts","../src/middleware/nextjs.ts","../src/middleware/express.ts"],"sourcesContent":["/**\n * apptvty — Server-side analytics and AEO SDK\n *\n * Quickstart (Next.js App Router):\n *\n * // middleware.ts\n * import { withApptvty } from 'apptvty/nextjs';\n * export default withApptvty({ apiKey: 'ak_...', siteId: 'site_...' });\n * export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };\n *\n * // app/query/route.ts ← your AEO query page\n * import { createNextjsQueryHandler } from 'apptvty/nextjs';\n * export const GET = createNextjsQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' });\n *\n * Quickstart (Express):\n *\n * import express from 'express';\n * import { createExpressMiddleware, createExpressQueryHandler } from 'apptvty/express';\n *\n * const app = express();\n * const config = { apiKey: 'ak_...', siteId: 'site_...' };\n * app.use(createExpressMiddleware(config));\n * app.get('/query', createExpressQueryHandler(config));\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\nexport type {\n ApptvtyConfig,\n AgentQueryResponse,\n AgentErrorResponse,\n QueryEndpointDiscovery,\n QuerySource,\n SponsoredAd,\n PageAd,\n PageAdsResponse,\n RequestLogEntry,\n CrawlerInfo,\n ImpressionLog,\n SiteOverviewStats,\n DailyStat,\n CrawlerBreakdown,\n RecentActivityItem,\n RecentQueryItem,\n SiteWalletInfo,\n} from './types.js';\n\n// ─── Core utilities (framework-agnostic) ──────────────────────────────────────\nexport { ApptvtyClient, ApptvtyApiError } from './client.js';\nexport { RequestLogger } from './logger.js';\nexport { detectCrawler, getKnownCrawlerNames } from './crawler.js';\nexport { createQueryHandler } from './query-handler.js';\n\n// ─── Next.js integration ──────────────────────────────────────────────────────\nexport { withApptvty, createNextjsQueryHandler } from './middleware/nextjs.js';\n\n// ─── Express integration ──────────────────────────────────────────────────────\nexport { createExpressMiddleware, createExpressQueryHandler } from './middleware/express.js';\n","/**\n * HTTP client for the Apptvty API.\n *\n * Handles:\n * - Batch log ingestion (/v1/logs/batch)\n * - Query processing (/v1/query) — returns answer + optional ad\n * - Impression logging (/v1/impressions) — triggers billing for ads\n */\n\nimport type {\n ApptvtyConfig,\n BackendQueryResponse,\n CrawlerBreakdown,\n DailyStat,\n ImpressionLog,\n PageAdsResponse,\n QueryRequest,\n RecentActivityItem,\n RecentQueryItem,\n RequestLogEntry,\n SiteOverviewStats,\n SiteWalletInfo,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.apptvty.com';\n\nfunction resolveBaseUrl(config: ApptvtyConfig): string {\n const raw: string =\n config.baseUrl ??\n (typeof process !== 'undefined' ? process.env?.APPTVTY_API_URL : undefined) ??\n DEFAULT_BASE_URL;\n return raw.replace(/\\/$/, '');\n}\n\nexport class ApptvtyClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly siteId: string;\n private readonly debug: boolean;\n\n constructor(config: ApptvtyConfig) {\n this.baseUrl = resolveBaseUrl(config);\n this.apiKey = config.apiKey;\n this.siteId = config.siteId;\n this.debug = config.debug ?? false;\n }\n\n /**\n * Send a batch of request log entries to the Apptvty ingestion API.\n * Called by the logger's auto-flush — not called directly by user code.\n */\n async sendLogs(logs: RequestLogEntry[]): Promise<void> {\n if (logs.length === 0) return;\n\n try {\n await this.post('/v1/logs/batch', { logs });\n this.log(`Flushed ${logs.length} log(s)`);\n } catch (err) {\n if (err instanceof ApptvtyTrialExpiredError) {\n // Always visible — coding agents and developers need to see this\n console.warn(\n `\\n[apptvty] ⚠ FREE TRIAL EXPIRED\\n` +\n `[apptvty] Log in and upgrade to keep receiving agent analytics.\\n` +\n `[apptvty] Dashboard: ${err.dashboardUrl}\\n`,\n );\n return;\n }\n // Other logging failures must never propagate to the user's request path\n this.warn('Failed to send logs:', err);\n }\n }\n\n /**\n * Send a query to the Apptvty backend.\n * The backend runs RAG against the site's indexed content and,\n * if ads are enabled for the site, attaches a relevant sponsored ad.\n *\n * @throws on non-retryable errors (network errors, 5xx) — callers should catch\n */\n async query(req: QueryRequest): Promise<BackendQueryResponse> {\n // ApptvtyTrialExpiredError propagates to the query handler, which surfaces it to the agent\n const response = await this.post<BackendQueryResponse>('/v1/query', req);\n return response;\n }\n\n /**\n * Get relevant ads for a page (for HTML injection when crawler is detected).\n * Used by middleware to inject ads into HTML responses.\n */\n async getAdsForPage(req: { site_id: string; page_path: string }): Promise<PageAdsResponse> {\n const response = await this.post<PageAdsResponse>('/v1/ads/for-page', req);\n return response;\n }\n\n /**\n * Log an ad impression back to Apptvty.\n *\n * Called after returning a query response that contains a `sponsored` block.\n * This is what triggers the billing cycle:\n * - Advertiser gets charged (debited from their USDC ad budget)\n * - Publisher (the website) gets credited in USDC\n *\n * This call is fire-and-forget. The SDK logs a warning on failure\n * but does not throw — a missed impression log is better than\n * breaking the query response.\n */\n async logImpression(impression: ImpressionLog): Promise<void> {\n try {\n await this.post('/v1/impressions', impression);\n this.log(`Impression logged: ${impression.impression_id}`);\n } catch (err) {\n this.warn('Failed to log impression (billing may be delayed):', err);\n }\n }\n\n // ─── Analytics (for coding agents) ───────────────────────────────────────────\n // These allow agents to check activity, logs, and errors without a human.\n\n /** Get 30-day traffic overview (requests, AI %, crawlers, queries). */\n async getSiteStats(): Promise<SiteOverviewStats> {\n return this.get<SiteOverviewStats>(`/v1/sites/${this.siteId}/stats`);\n }\n\n /** Get day-by-day stats (default 30 days, max 90). */\n async getSiteDailyStats(days = 30): Promise<{ days: number; stats: DailyStat[] }> {\n return this.get(`/v1/sites/${this.siteId}/stats/daily`, { days: String(days) });\n }\n\n /** Get crawler breakdown by type (default 30 days). */\n async getSiteCrawlers(days = 30): Promise<{ days: number; crawlers: CrawlerBreakdown[] }> {\n return this.get(`/v1/sites/${this.siteId}/crawlers`, { days: String(days) });\n }\n\n /** Get recent activity feed (last hour, default 50 items, max 200). */\n async getSiteActivity(limit = 50): Promise<{ activity: RecentActivityItem[] }> {\n return this.get(`/v1/sites/${this.siteId}/activity`, { limit: String(limit) });\n }\n\n /** Get recent agent queries (default 50, max 200). */\n async getSiteQueries(limit = 50): Promise<{ queries: RecentQueryItem[] }> {\n return this.get(`/v1/sites/${this.siteId}/queries`, { limit: String(limit) });\n }\n\n /** Get wallet balance and earnings. */\n async getSiteWallet(): Promise<SiteWalletInfo> {\n return this.get<SiteWalletInfo>(`/v1/sites/${this.siteId}/wallet`);\n }\n\n private async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));\n }\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'User-Agent': 'apptvty-sdk/0.1.0',\n },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new ApptvtyApiError(response.status, path, text);\n }\n return response.json() as Promise<T>;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'apptvty-sdk/0.1.0',\n },\n body: JSON.stringify(body),\n // Node 18+ fetch: set a reasonable timeout via AbortSignal\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n if (response.status === 402) {\n let dashboardUrl = 'https://dashboard.apptvty.com/login';\n try {\n const json = JSON.parse(text);\n dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;\n } catch {}\n throw new ApptvtyTrialExpiredError(dashboardUrl);\n }\n throw new ApptvtyApiError(response.status, path, text);\n }\n\n return response.json() as Promise<T>;\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[apptvty]', ...args);\n }\n\n private warn(...args: unknown[]): void {\n if (this.debug) console.warn('[apptvty]', ...args);\n }\n}\n\nexport class ApptvtyApiError extends Error {\n constructor(\n public readonly statusCode: number,\n public readonly path: string,\n public readonly body: string,\n ) {\n super(`Apptvty API error ${statusCode} at ${path}: ${body}`);\n this.name = 'ApptvtyApiError';\n }\n}\n\nexport class ApptvtyTrialExpiredError extends Error {\n constructor(public readonly dashboardUrl: string) {\n super(`Apptvty free trial has expired. Log in to continue: ${dashboardUrl}`);\n this.name = 'ApptvtyTrialExpiredError';\n }\n}\n","/**\n * Batched request logger.\n *\n * Queues RequestLogEntry objects in memory and flushes them in batches\n * to the Apptvty API. This keeps per-request overhead near zero.\n *\n * Flush triggers:\n * 1. Queue reaches batchSize (default 50)\n * 2. flushInterval elapses (default 5s)\n * 3. process.exit / SIGTERM (best-effort sync flush)\n */\n\nimport type { ApptvtyClient } from './client.js';\nimport type { ApptvtyConfig, RequestLogEntry } from './types.js';\n\nexport class RequestLogger {\n private queue: RequestLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private flushing = false;\n private readonly batchSize: number;\n private readonly debug: boolean;\n\n constructor(\n private readonly client: ApptvtyClient,\n config: ApptvtyConfig,\n ) {\n this.batchSize = config.batchSize ?? 50;\n this.debug = config.debug ?? false;\n\n const interval = config.flushInterval ?? 5_000;\n this.timer = setInterval(() => { void this.flush(); }, interval);\n\n // Unref so the timer doesn't keep the process alive\n if (this.timer && typeof (this.timer as any).unref === 'function') {\n (this.timer as any).unref();\n }\n\n // Best-effort flush on process shutdown (Node-only)\n if (typeof process !== 'undefined' && typeof process.once === 'function') {\n const handleExit = () => { void this.flushSync(); };\n try {\n process.once('SIGTERM', handleExit);\n process.once('SIGINT', handleExit);\n process.once('beforeExit', handleExit);\n } catch {\n // Ignore errors in environments where process exists but signal listeners don't\n }\n }\n }\n\n /** Enqueue a single log entry. Non-blocking. */\n enqueue(entry: RequestLogEntry): void {\n this.queue.push(entry);\n if (this.queue.length >= this.batchSize) {\n // Don't await — fire and forget\n void this.flush();\n }\n }\n\n /** Flush the current queue to the API. */\n async flush(): Promise<void> {\n if (this.flushing || this.queue.length === 0) return;\n\n this.flushing = true;\n const batch = this.queue.splice(0, this.batchSize);\n\n try {\n await this.client.sendLogs(batch);\n } catch {\n // Already handled (logged) inside client.sendLogs — nothing to do here\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Synchronous-ish flush for process shutdown.\n * Fires the fetch and doesn't await to avoid blocking exit handlers.\n */\n private flushSync(): void {\n if (this.queue.length === 0) return;\n const batch = this.queue.splice(0);\n // Fire without await — best effort on exit\n void this.client.sendLogs(batch);\n }\n\n /** Stop the interval timer. Call when you want to fully tear down the SDK. */\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n void this.flush();\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[apptvty:logger]', ...args);\n }\n}\n\n// ─── Helpers used by middleware to build log entries ──────────────────────────\n\nexport function getClientIp(headers: Record<string, string | string[] | undefined>): string {\n const forwarded = headers['x-forwarded-for'];\n if (forwarded) {\n const first = Array.isArray(forwarded) ? forwarded[0] : forwarded;\n return first.split(',')[0].trim();\n }\n return (headers['x-real-ip'] as string) ?? 'unknown';\n}\n","/**\n * Lightweight AI crawler detection.\n *\n * This is the single source of truth for crawler classification in the SDK.\n * The backend (Python analytics API and TypeScript handlers) should eventually\n * consume this same list rather than maintaining separate copies.\n */\n\nimport type { CrawlerInfo } from './types.js';\n\ninterface KnownCrawler {\n name: string;\n organization: string;\n patterns: RegExp[];\n}\n\nconst KNOWN_CRAWLERS: KnownCrawler[] = [\n // OpenAI\n { name: 'GPTBot', organization: 'OpenAI', patterns: [/GPTBot/i, /ChatGPT-User/i] },\n { name: 'OpenAI-SearchBot', organization: 'OpenAI', patterns: [/OpenAI-SearchBot/i] },\n // Anthropic\n { name: 'ClaudeBot', organization: 'Anthropic', patterns: [/ClaudeBot/i, /Claude-Web/i, /Anthropic-AI/i] },\n // Google\n { name: 'Google-Extended', organization: 'Google AI', patterns: [/Google-Extended/i] },\n { name: 'GoogleOther', organization: 'Google AI', patterns: [/GoogleOther/i] },\n { name: 'Googlebot', organization: 'Google', patterns: [/Googlebot/i] },\n // Microsoft\n { name: 'Bingbot', organization: 'Microsoft', patterns: [/bingbot/i, /BingPreview/i] },\n // Perplexity\n { name: 'PerplexityBot', organization: 'Perplexity', patterns: [/PerplexityBot/i] },\n // You.com\n { name: 'YouBot', organization: 'You.com', patterns: [/YouBot/i] },\n // Meta\n { name: 'Meta-ExternalAgent', organization: 'Meta', patterns: [/Meta-ExternalAgent/i] },\n { name: 'FacebookBot', organization: 'Meta', patterns: [/facebookexternalhit/i, /FacebookBot/i] },\n // Apple\n { name: 'AppleBot', organization: 'Apple', patterns: [/Applebot/i] },\n // Twitter/X\n { name: 'TwitterBot', organization: 'Twitter/X', patterns: [/Twitterbot/i] },\n // LinkedIn\n { name: 'LinkedInBot', organization: 'LinkedIn', patterns: [/LinkedInBot/i] },\n // DuckDuckGo\n { name: 'DuckDuckBot', organization: 'DuckDuckGo', patterns: [/DuckDuckBot/i] },\n // Cohere\n { name: 'Cohere-AI', organization: 'Cohere', patterns: [/Cohere-AI/i, /cohere-ai/i] },\n // Allen Institute\n { name: 'AI2Bot', organization: 'Allen Institute for AI', patterns: [/AI2Bot/i] },\n // Mistral\n { name: 'MistralBot', organization: 'Mistral AI', patterns: [/MistralBot/i] },\n];\n\n/**\n * Patterns that reliably indicate AI/LLM-related traffic.\n * Each entry is [pattern, confidence].\n */\nconst AI_PATTERN_MATCHES: [RegExp, number][] = [\n [/openai/i, 0.90],\n [/anthropic/i, 0.90],\n [/gpt|chatgpt/i, 0.85],\n [/claude/i, 0.85],\n [/perplexity/i, 0.85],\n [/llm|language[- ]model/i, 0.75],\n [/bot.*ai|ai.*bot/i, 0.75],\n [/search.*ai|ai.*search/i, 0.70],\n];\n\n/**\n * Patterns that strongly suggest a human browser.\n * We check these before generic bot scoring to reduce false positives.\n * NOTE: We deliberately exclude Chrome/Safari/Firefox from this list because\n * many bots spoof those strings. We only short-circuit on strings that bots\n * have little reason to include.\n */\nconst HUMAN_SIGNALS: RegExp[] = [\n /Mozilla\\/5\\.0.*\\(Windows NT.*\\) AppleWebKit.*Chrome.*Safari/i,\n /Mozilla\\/5\\.0.*\\(Macintosh.*\\) AppleWebKit.*Version.*Safari/i,\n];\n\nexport function detectCrawler(userAgent: string): CrawlerInfo {\n if (!userAgent || userAgent.length < 4) {\n return { isAi: false, name: null, organization: null, confidence: 0.3, detectionMethod: 'heuristic' };\n }\n\n // 1. Exact match against known crawlers — highest confidence\n for (const crawler of KNOWN_CRAWLERS) {\n for (const pattern of crawler.patterns) {\n if (pattern.test(userAgent)) {\n return {\n isAi: true,\n name: crawler.name,\n organization: crawler.organization,\n confidence: 0.95,\n detectionMethod: 'exact_match',\n };\n }\n }\n }\n\n // 2. Short-circuit for strong human browser signals\n for (const pattern of HUMAN_SIGNALS) {\n if (pattern.test(userAgent)) {\n return { isAi: false, name: null, organization: null, confidence: 0.85, detectionMethod: 'heuristic' };\n }\n }\n\n // 3. Pattern-based AI detection\n for (const [pattern, confidence] of AI_PATTERN_MATCHES) {\n if (pattern.test(userAgent)) {\n return {\n isAi: true,\n name: extractBotName(userAgent),\n organization: null,\n confidence,\n detectionMethod: 'pattern_match',\n };\n }\n }\n\n // 4. Generic heuristic scoring for unknown bots\n let score = 0;\n if (/bot|crawler|spider|scraper/i.test(userAgent)) score += 0.4;\n if (/python-requests|curl\\/|wget\\/|scrapy|go-http-client/i.test(userAgent)) score += 0.3;\n if (!userAgent.includes('Mozilla')) score += 0.2;\n if (userAgent.length < 20) score += 0.2;\n\n if (score >= 0.5) {\n return {\n isAi: false, // Generic bot — not classified as AI\n name: 'unknown_bot',\n organization: null,\n confidence: Math.min(score, 0.8),\n detectionMethod: 'heuristic',\n };\n }\n\n return { isAi: false, name: null, organization: null, confidence: 0.1, detectionMethod: 'none' };\n}\n\nfunction extractBotName(userAgent: string): string {\n const parts = userAgent.split(/[\\s/;(]+/);\n for (const part of parts) {\n if (/bot|agent|crawler|ai/i.test(part) && part.length > 2) {\n return part.replace(/[^a-zA-Z0-9-_]/g, '');\n }\n }\n return 'unknown_ai_bot';\n}\n\n/** Returns the list of all known crawlers for reference (e.g. for agents.txt generation) */\nexport function getKnownCrawlerNames(): string[] {\n return KNOWN_CRAWLERS.map(c => c.name);\n}\n","/**\n * Framework-agnostic query endpoint handler.\n *\n * Serves the site's AEO (Agent Experience Optimization) query page.\n *\n * GET /query → Returns a self-describing discovery JSON\n * GET /query?q=<question> → Returns an AI-generated answer from the site's\n * indexed content, plus a sponsored ad if ads are\n * enabled and a matching ad exists.\n *\n * When an ad is included in the response, this handler fires an async\n * impression log back to Apptvty to trigger billing:\n * - Advertiser is charged (debited from their USDC ad budget)\n * - Publisher earns USDC (credited to their Crossmint wallet)\n */\n\nimport type { ApptvtyClient } from './client.js';\nimport { ApptvtyTrialExpiredError } from './client.js';\nimport type { ApptvtyConfig } from './types.js';\nimport type {\n AgentErrorResponse,\n AgentQueryResponse,\n QueryEndpointDiscovery,\n} from './types.js';\n\nexport interface QueryHandlerRequest {\n query: string | null; // ?q= param, null if not present\n lang: string | null; // ?lang= param\n surface_ads?: boolean; // ?surface_ads=1|0 — include ads (default true)\n ai_crawler?: boolean; // ?ai_crawler=1 — \"I'm an agent crawling, surface ads\"\n userAgent: string;\n ipAddress: string;\n /** Full URL of the request, used to build the example in the discovery response */\n requestUrl: string;\n}\n\nexport interface QueryHandlerResponse {\n status: number;\n body: AgentQueryResponse | QueryEndpointDiscovery | AgentErrorResponse;\n headers: Record<string, string>;\n}\n\nconst RESPONSE_HEADERS: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-store',\n 'X-Robots-Tag': 'noindex', // Don't index the query endpoint itself\n};\n\nexport function createQueryHandler(client: ApptvtyClient, config: ApptvtyConfig) {\n const queryPath = config.queryPath ?? '/query';\n\n return async function handleQuery(req: QueryHandlerRequest): Promise<QueryHandlerResponse> {\n // ── Discovery response (no ?q= param) ──────────────────────────────────\n if (!req.query || req.query.trim() === '') {\n const origin = getOrigin(req.requestUrl);\n const discovery: QueryEndpointDiscovery = {\n version: '1.0',\n endpoint: `${origin}${queryPath}`,\n description:\n 'Query this site\\'s content directly. Submit a question and receive a structured JSON ' +\n 'response with an AI-generated answer, source citations, and confidence score. ' +\n 'Ideal for AI agents, search engines, and automated systems seeking direct answers.',\n crawl_policy:\n 'Do not crawl this entire site unnecessarily. Use this query endpoint with ?q=your_question to get the relevant information you need. Full-site crawling is discouraged.',\n usage: {\n method: 'GET',\n parameters: {\n q: {\n type: 'string',\n required: true,\n description: 'Your question or search query (max 500 characters)',\n },\n lang: {\n type: 'string',\n required: false,\n description: 'Preferred response language (ISO 639-1 code, default: en)',\n },\n surface_ads: {\n type: 'boolean',\n required: false,\n description: 'Include sponsored ads in response (1 = yes, default; 0 = no). When crawling, use ai_crawler=1 to signal you want ads.',\n },\n ai_crawler: {\n type: 'boolean',\n required: false,\n description: 'Set to 1 when you are an AI agent/crawler. Signals the server to surface ads. Use with ?q=... for targeted answers + ads.',\n },\n },\n example: `${origin}${queryPath}?q=what+features+do+you+offer&ai_crawler=1`,\n response_format: 'application/json',\n },\n capabilities: [\n 'Natural language question answering',\n 'Source citations with relevance scores',\n 'Structured JSON for easy parsing',\n 'Supports follow-up context via session continuity',\n 'Query-preferred: use ?q= instead of full-site crawl',\n ],\n rate_limit: '100 requests per hour per IP',\n };\n\n return { status: 200, body: discovery, headers: RESPONSE_HEADERS };\n }\n\n // ── Validate query ──────────────────────────────────────────────────────\n const trimmedQuery = req.query.trim();\n if (trimmedQuery.length > 500) {\n return errorResponse(400, 'QUERY_TOO_LONG', 'Query must be 500 characters or fewer');\n }\n\n // ── Call Apptvty backend (RAG + optional ad) ────────────────────────────\n const requestId = crypto.randomUUID();\n const timestamp = new Date().toISOString();\n const startMs = Date.now();\n\n const surfaceAds = req.surface_ads !== false; // default true\n const aiCrawler = req.ai_crawler === true;\n\n let backendResponse;\n try {\n backendResponse = await client.query({\n site_id: config.siteId,\n query: trimmedQuery,\n agent_ua: req.userAgent,\n agent_ip: req.ipAddress,\n request_id: requestId,\n timestamp,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n });\n } catch (err) {\n if (err instanceof ApptvtyTrialExpiredError) {\n return errorResponse(\n 402,\n 'TRIAL_EXPIRED',\n `Apptvty free trial has expired. The site owner must log in and upgrade to continue. Dashboard: ${err.dashboardUrl}`,\n );\n }\n return errorResponse(502, 'UPSTREAM_ERROR', 'Could not retrieve an answer at this time');\n }\n\n const responseTimeMs = Date.now() - startMs;\n\n // ── Log impression if an ad was served ─────────────────────────────────\n //\n // This is fire-and-forget. We do NOT await it — the agent gets its\n // response immediately, and the billing event is recorded asynchronously.\n //\n // Billing flow triggered by these calls (one per ad):\n // 1. Apptvty records impression_id + metadata\n // 2. Advertiser's USDC ad budget is debited\n // 3. Publisher's Crossmint wallet is credited\n const ads = backendResponse.sponsored\n ? (Array.isArray(backendResponse.sponsored) ? backendResponse.sponsored : [backendResponse.sponsored])\n : [];\n for (const ad of ads) {\n void client.logImpression({\n impression_id: ad.impression_id,\n site_id: config.siteId,\n query: trimmedQuery,\n agent_ua: req.userAgent,\n agent_ip: req.ipAddress,\n timestamp,\n });\n }\n\n // ── Build agent response ────────────────────────────────────────────────\n const agentResponse: AgentQueryResponse = {\n success: true,\n version: '1.0',\n query: trimmedQuery,\n answer: backendResponse.answer,\n sources: backendResponse.sources,\n confidence: backendResponse.confidence,\n ...(backendResponse.sponsored && { sponsored: backendResponse.sponsored }),\n metadata: {\n request_id: requestId,\n response_time_ms: responseTimeMs,\n tokens_used: backendResponse.tokens_used,\n site_id: config.siteId,\n timestamp,\n },\n };\n\n return { status: 200, body: agentResponse, headers: RESPONSE_HEADERS };\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction errorResponse(\n status: number,\n code: string,\n message: string,\n): QueryHandlerResponse {\n const body: AgentErrorResponse = {\n success: false,\n error: {\n code,\n message,\n request_id: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n },\n };\n return { status, body, headers: RESPONSE_HEADERS };\n}\n\nfunction getOrigin(url: string): string {\n try {\n const parsed = new URL(url);\n return parsed.origin;\n } catch {\n return '';\n }\n}\n","/**\n * Next.js App Router integration for the Apptvty SDK.\n *\n * Usage — two pieces:\n *\n * 1. Wrap your existing middleware.ts (or use ours standalone):\n *\n * import { withApptvty } from 'apptvty/nextjs';\n * export default withApptvty({ apiKey: 'ak_...', siteId: 'site_...' });\n * // or wrap your existing middleware:\n * export default withApptvty(config, yourMiddleware);\n *\n * 2. Create app/query/route.ts for the AEO query endpoint:\n *\n * import { createNextjsQueryHandler } from 'apptvty/nextjs';\n * export const GET = createNextjsQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' });\n *\n * The SDK logs all requests (especially agentic traffic) to Apptvty\n * and serves structured AI answers + optional sponsored ads on /query.\n */\n\nimport type { NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { ApptvtyClient } from '../client.js';\nimport { detectCrawler } from '../crawler.js';\nimport { RequestLogger, getClientIp } from '../logger.js';\nimport { createQueryHandler } from '../query-handler.js';\nimport type { ApptvtyConfig, RequestLogEntry } from '../types.js';\n\n/** Convert Next request headers to a plain object (Web API Headers.entries() at runtime). */\nfunction headersToRecord(h: NextRequest['headers']): Record<string, string> {\n const entries = (h as unknown as { entries(): IterableIterator<[string, string]> }).entries();\n return Object.fromEntries(Array.from(entries));\n}\n\n// ─── Shared singleton instances per config (keyed by apiKey) ─────────────────\n\nconst instances = new Map<string, { client: ApptvtyClient; logger: RequestLogger }>();\n\nfunction getInstance(config: ApptvtyConfig) {\n const key = config.apiKey;\n if (!instances.has(key)) {\n const client = new ApptvtyClient(config);\n const logger = new RequestLogger(client, config);\n instances.set(key, { client, logger });\n }\n return instances.get(key)!;\n}\n\n// ─── Middleware wrapper ───────────────────────────────────────────────────────\n\ntype NextMiddleware = (request: NextRequest) => Response | NextResponse | Promise<Response | NextResponse>;\n\n/**\n * Wraps a Next.js middleware function (or creates a passthrough) with\n * Apptvty request logging. All requests passing through middleware are\n * logged to Apptvty, with AI crawlers automatically classified.\n *\n * @example\n * // middleware.ts\n * import { withApptvty } from 'apptvty/nextjs';\n * export default withApptvty({ apiKey: 'ak_...', siteId: 'site_...' });\n */\nexport function withApptvty(\n config: ApptvtyConfig,\n next?: NextMiddleware,\n): NextMiddleware {\n const { client, logger } = getInstance(config);\n const queryPath = config.queryPath ?? '/query';\n\n return async function apptvtyMiddleware(request: NextRequest): Promise<NextResponse> {\n const startMs = Date.now();\n const userAgent = request.headers.get('user-agent') ?? '';\n const crawlerInfo = detectCrawler(userAgent);\n const aiCrawlerParam = parseBoolParam(request.nextUrl.searchParams.get('ai_crawler'), false);\n const isCrawler = crawlerInfo.isAi || aiCrawlerParam;\n\n // Run the user's middleware (or passthrough)\n let response: Response | NextResponse;\n try {\n response = next ? await next(request) : NextResponse.next();\n } catch (err) {\n // Don't swallow application errors\n throw err;\n }\n\n const responseTimeMs = Date.now() - startMs;\n\n // Skip logging for Next.js internals and static assets\n const { pathname } = request.nextUrl;\n if (shouldSkip(pathname)) {\n return response as NextResponse;\n }\n\n const headers = headersToRecord(request.headers);\n const entry: RequestLogEntry = {\n site_id: config.siteId,\n timestamp: new Date().toISOString(),\n method: request.method,\n path: pathname,\n status_code: response.status,\n response_time_ms: responseTimeMs,\n ip_address: getClientIp(headers),\n user_agent: userAgent,\n referer: request.headers.get('referer'),\n is_ai_crawler: crawlerInfo.isAi,\n crawler_type: crawlerInfo.name,\n crawler_organization: crawlerInfo.organization,\n confidence_score: crawlerInfo.confidence,\n };\n\n logger.enqueue(entry);\n\n // Ad injection: when crawler hits an HTML page, inject sponsored ads\n if (isCrawler && response.ok && !pathname.startsWith(queryPath)) {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('text/html')) {\n try {\n const modified = await injectAdsIntoHtml(response, client, config.siteId, pathname);\n if (modified) return modified;\n } catch (err) {\n // Never break the response for ad injection failures\n if (config.debug) console.warn('[apptvty] Ad injection failed:', err);\n }\n }\n }\n\n return response as NextResponse;\n };\n}\n\n// ─── Query route handler ──────────────────────────────────────────────────────\n\n/**\n * Creates a Next.js App Router GET handler for the AEO query endpoint.\n *\n * @example\n * // app/query/route.ts\n * import { createNextjsQueryHandler } from 'apptvty/nextjs';\n * export const GET = createNextjsQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' });\n */\nexport function createNextjsQueryHandler(config: ApptvtyConfig) {\n const { client } = getInstance(config);\n const handleQuery = createQueryHandler(client, config);\n\n return async function GET(request: NextRequest): Promise<NextResponse> {\n const { searchParams } = request.nextUrl;\n const q = searchParams.get('q');\n const lang = searchParams.get('lang');\n const surfaceAds = parseBoolParam(searchParams.get('surface_ads'), true);\n const aiCrawler = parseBoolParam(searchParams.get('ai_crawler'), false);\n const userAgent = request.headers.get('user-agent') ?? '';\n const headers = headersToRecord(request.headers);\n\n const result = await handleQuery({\n query: q,\n lang,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n userAgent,\n ipAddress: getClientIp(headers),\n requestUrl: request.url,\n });\n\n return NextResponse.json(result.body, {\n status: result.status,\n headers: result.headers,\n });\n };\n}\n\n// ─── HTML ad injection ────────────────────────────────────────────────────────\n\nconst AD_INJECTION_MARKER = '<!-- apptvty-sponsored -->';\n\nfunction buildAdBlock(ads: Array<{ text: string; url: string; advertiser: string }>): string {\n const items = ads\n .map(\n (ad) =>\n `<li><a href=\"${escapeHtml(ad.url)}\" rel=\"nofollow\">${escapeHtml(ad.text)}</a> <small>— ${escapeHtml(ad.advertiser)}</small></li>`\n )\n .join('\\n');\n return `\n<section aria-label=\"Sponsored\" data-sponsored ${AD_INJECTION_MARKER}>\n <h3>Sponsored</h3>\n <ul>${items}</ul>\n</section>`;\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nasync function injectAdsIntoHtml(\n response: Response,\n client: ApptvtyClient,\n siteId: string,\n pathname: string\n): Promise<NextResponse | null> {\n const html = await response.text();\n if (!html || html.includes(AD_INJECTION_MARKER)) return null;\n\n const pageAds = await client.getAdsForPage({ site_id: siteId, page_path: pathname });\n if (!pageAds.ads || pageAds.ads.length === 0) return null;\n\n const adBlock = buildAdBlock(pageAds.ads);\n let modified: string;\n\n if (html.includes('</body>')) {\n modified = html.replace('</body>', `${adBlock}\\n</body>`);\n } else if (html.includes('</html>')) {\n modified = html.replace('</html>', `${adBlock}\\n</html>`);\n } else {\n modified = html + adBlock;\n }\n\n return new NextResponse(modified, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction parseBoolParam(value: string | null, defaultValue: boolean): boolean {\n if (value === null) return defaultValue;\n return value === '1' || value === 'true' || value === 'yes';\n}\n\nfunction shouldSkip(pathname: string): boolean {\n return (\n pathname.startsWith('/_next/') ||\n pathname.startsWith('/api/_') ||\n pathname === '/favicon.ico' ||\n /\\.(svg|png|jpg|jpeg|gif|webp|ico|woff2?|ttf|css|js\\.map)$/.test(pathname)\n );\n}\n","/**\n * Express / Node.js integration for the Apptvty SDK.\n *\n * Usage:\n *\n * import express from 'express';\n * import { createExpressMiddleware, createExpressQueryHandler } from 'apptvty/express';\n *\n * const app = express();\n * const config = { apiKey: 'ak_...', siteId: 'site_...' };\n *\n * // 1. Log all traffic\n * app.use(createExpressMiddleware(config));\n *\n * // 2. Mount the AEO query page\n * app.get('/query', createExpressQueryHandler(config));\n *\n * Works with any Connect-compatible framework (Express, Fastify via @fastify/express, etc.)\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { ApptvtyClient } from '../client.js';\nimport { detectCrawler } from '../crawler.js';\nimport { RequestLogger, getClientIp } from '../logger.js';\nimport { createQueryHandler } from '../query-handler.js';\nimport type { ApptvtyConfig, RequestLogEntry } from '../types.js';\n\nexport type ConnectMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n) => void;\n\nexport type ConnectHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => void | Promise<void>;\n\n// ─── Shared singleton instances per config ────────────────────────────────────\n\nconst instances = new Map<string, { client: ApptvtyClient; logger: RequestLogger }>();\n\nfunction getInstance(config: ApptvtyConfig) {\n const key = config.apiKey;\n if (!instances.has(key)) {\n const client = new ApptvtyClient(config);\n const logger = new RequestLogger(client, config);\n instances.set(key, { client, logger });\n }\n return instances.get(key)!;\n}\n\n// ─── Traffic logging middleware ───────────────────────────────────────────────\n\n/**\n * Express middleware that logs every request to Apptvty.\n * Captures: method, path, status, response time, user-agent, IP, crawler classification.\n *\n * Mount this before your routes so all traffic is captured.\n *\n * @example\n * app.use(createExpressMiddleware({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressMiddleware(config: ApptvtyConfig): ConnectMiddleware {\n const { logger } = getInstance(config);\n\n return function apptvtyLogger(req, res, next) {\n const startMs = Date.now();\n const userAgent = req.headers['user-agent'] ?? '';\n const crawlerInfo = detectCrawler(userAgent);\n const path = req.url ?? '/';\n\n // Intercept response finish to capture status code and timing\n res.on('finish', () => {\n if (shouldSkip(path)) return;\n\n const entry: RequestLogEntry = {\n site_id: config.siteId,\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path,\n status_code: res.statusCode,\n response_time_ms: Date.now() - startMs,\n ip_address: getClientIp(req.headers as Record<string, string | string[] | undefined>),\n user_agent: userAgent,\n referer: (req.headers['referer'] as string) ?? null,\n is_ai_crawler: crawlerInfo.isAi,\n crawler_type: crawlerInfo.name,\n crawler_organization: crawlerInfo.organization,\n confidence_score: crawlerInfo.confidence,\n };\n\n logger.enqueue(entry);\n });\n\n next();\n };\n}\n\n// ─── Query endpoint handler ───────────────────────────────────────────────────\n\n/**\n * Express route handler for the AEO query endpoint.\n *\n * Mount this at the path configured in your dashboard (default: /query).\n *\n * @example\n * app.get('/query', createExpressQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressQueryHandler(config: ApptvtyConfig): ConnectHandler {\n const { client } = getInstance(config);\n const handleQuery = createQueryHandler(client, config);\n\n return async function queryHandler(req, res) {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const q = url.searchParams.get('q');\n const lang = url.searchParams.get('lang');\n const surfaceAds = parseBoolParam(url.searchParams.get('surface_ads'), true);\n const aiCrawler = parseBoolParam(url.searchParams.get('ai_crawler'), false);\n const userAgent = req.headers['user-agent'] ?? '';\n\n const result = await handleQuery({\n query: q,\n lang,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n userAgent,\n ipAddress: getClientIp(req.headers as Record<string, string | string[] | undefined>),\n requestUrl: url.toString(),\n });\n\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n res.statusCode = result.status;\n res.end(JSON.stringify(result.body));\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction parseBoolParam(value: string | null, defaultValue: boolean): boolean {\n if (value === null) return defaultValue;\n return value === '1' || value === 'true' || value === 'yes';\n}\n\nfunction shouldSkip(path: string): boolean {\n return (\n path.startsWith('/_next/') ||\n /\\.(svg|png|jpg|jpeg|gif|webp|ico|woff2?|ttf|css|js\\.map)$/.test(path)\n );\n}\n"],"mappings":"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","names":["instances","getInstance","shouldSkip","parseBoolParam"]}
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createNextjsQueryHandler,
3
3
  withApptvty
4
- } from "./chunk-XOWRKLFM.mjs";
4
+ } from "./chunk-OZAZWZNO.mjs";
5
5
  import {
6
6
  createExpressMiddleware,
7
7
  createExpressQueryHandler
8
- } from "./chunk-RGUS6IL6.mjs";
8
+ } from "./chunk-3ITWIW4P.mjs";
9
9
  import {
10
10
  ApptvtyApiError,
11
11
  ApptvtyClient,
@@ -13,7 +13,7 @@ import {
13
13
  createQueryHandler,
14
14
  detectCrawler,
15
15
  getKnownCrawlerNames
16
- } from "./chunk-WATTAPBA.mjs";
16
+ } from "./chunk-JVOMOEEL.mjs";
17
17
  export {
18
18
  ApptvtyApiError,
19
19
  ApptvtyClient,
@@ -319,13 +319,20 @@ var RequestLogger = class {
319
319
  this.timer = setInterval(() => {
320
320
  void this.flush();
321
321
  }, interval);
322
- if (this.timer.unref) this.timer.unref();
323
- const handleExit = () => {
324
- void this.flushSync();
325
- };
326
- process.once("SIGTERM", handleExit);
327
- process.once("SIGINT", handleExit);
328
- process.once("beforeExit", handleExit);
322
+ if (this.timer && typeof this.timer.unref === "function") {
323
+ this.timer.unref();
324
+ }
325
+ if (typeof process !== "undefined" && typeof process.once === "function") {
326
+ const handleExit = () => {
327
+ void this.flushSync();
328
+ };
329
+ try {
330
+ process.once("SIGTERM", handleExit);
331
+ process.once("SIGINT", handleExit);
332
+ process.once("beforeExit", handleExit);
333
+ } catch {
334
+ }
335
+ }
329
336
  }
330
337
  /** Enqueue a single log entry. Non-blocking. */
331
338
  enqueue(entry) {