CrawlPodScan your site

API reference

Every signature below was read directly from the installed package's type declarations and compiled source (v0.3.0), not the README — a couple of real, small discrepancies from the README are noted where found.

ai-visibility/detector

Zero runtime dependencies. Edge-safe.

detectBot(userAgent: string): BotInfo | null

Framework-agnostic function. Returns the matched bot's info, or null.

AI_CRAWLERS: BotInfo[]

The full list of 15 known crawlers, each with a name, company, User-Agent match pattern, and purpose ('training' | 'search' | 'indexing' | 'unknown'):

GPTBot, ChatGPT-User, ClaudeBot, Claude-Web, PerplexityBot,
Google-Extended, Googlebot, Bingbot, CCBot, YouBot,
cohere-ai, meta-externalagent, Applebot-Extended, Diffbot, Bytespider

The package README abbreviates a few of these as "Cohere", "Meta", and "Apple" — the actual matched User-Agent substrings are cohere-ai, meta-externalagent, and Applebot-Extended, listed above as they actually appear in code.

class AIBotDetector

new AIBotDetector(config?: { additionalBots?: string[]; ignoreBots?: string[] })
  .detect(userAgent: string): BotInfo | null
  .getBotNames(): string[]

class HTMLOptimizer

Strips non-essential content from HTML for AI crawler consumption — keeps semantic structure, JSON-LD, and meaningful text.

new HTMLOptimizer(options?: AIOptimizationOptions)
  .optimize(html: string): string

interface AIOptimizationOptions {
  stripJs?: boolean;          // remove <script> tags except JSON-LD
  removeAds?: boolean;
  removeTracking?: boolean;
  simplifyNav?: boolean;
  structureContent?: boolean; // front-load main content
}

ai-visibility/schema

Dependency-free except fromHTML(), which lazily loads cheerioon first call (dynamic import — importing this subpath doesn't pull cheerio in unless you actually call it). All builders except offer()/aggregateRating() return a full JSON-LD object with @context; those two return a nested node meant to be embedded via product()'s or softwareApplication()'s offers/aggregateRating fields.

class SchemaBuilder {
  static faq(items: { q: string; a: string }[]): SchemaObject
  static product(data: ProductSchemaData): SchemaObject
  static article(data: ArticleSchemaData): SchemaObject
  static organization(data: OrganizationSchemaData): SchemaObject
  static person(data: PersonSchemaData): SchemaObject
  static website(data: WebSiteSchemaData): SchemaObject
  static softwareApplication(data: SoftwareApplicationSchemaData): SchemaObject
  static breadcrumbList(items: BreadcrumbItem[], options?: { baseUrl?: string }): SchemaObject
  static definedTerm(data: DefinedTermSchemaData): SchemaObject
  static definedTermSet(data: DefinedTermSetSchemaData): SchemaObject
  static offer(data: OfferSchemaData): SchemaObject          // nested, no @context
  static aggregateRating(data: AggregateRatingSchemaData): SchemaObject  // nested, no @context
  static fromHTML(html: string, hints?: { author?: string; publisher?: string }): Promise<SchemaObject>
  static toScriptTag(schema: SchemaObject): string
  static toScriptTagMultiple(schemas: SchemaObject[]): string
}

toScriptTag()/toScriptTagMultiple() return a full <script>…</script>string for raw-template injection — it doesn't fit React's dangerouslySetInnerHTML, which expects the tag's contents, not the tag itself. In React, use the plain object the builders return:

React/Next.js
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify(SchemaBuilder.faq([{ q: "...", a: "..." }]))
  }}
/>

ai-visibility/generators

class RobotsGenerator {
  constructor(config?: RobotsConfig)
  generate(): string
  static allowAll(options?: { disallow?: string[]; sitemapUrl?: string }): string
  static blockTraining(options?: { disallow?: string[]; sitemapUrl?: string }): string
}

interface RobotsConfig {
  allowAI?: string[];
  blockAI?: string[];
  disallow?: string[];   // default: [] as of 0.3.0 — see note below
  sitemapUrl?: string;
  crawlDelay?: number;
}

0.3.0 changed the default disallow list from ['/admin', '/api', '/private', '/_next', '/static'] to []— the old default meant every Next.js site using it was telling AI crawlers not to fetch the site's own JS/CSS chunks. Confirmed directly in the compiled source while migrating this site in Phase 3. If you're upgrading from 0.2.x and relied on the default, pass your own disallow list explicitly now.

class LLMSTextGenerator {
  constructor(config: LLMSConfig)
  generate(): Promise<string>
  static minimal(config: LLMSConfig): string   // no summaries, synchronous
}

interface LLMSConfig {
  siteName: string;
  description: string;
  baseUrl?: string;
  pages: { url: string; title: string; summary?: string; priority?: "high" | "medium" | "low" }[];
  contact?: { email?: string; twitter?: string; github?: string };
  autoSummarize?: boolean; // fetches each page live to extract a summary if one isn't given
}

ai-visibility/express

Node-only. Imports types from express (optional peer dependency).

createAIMiddleware(config?: AIMiddlewareConfig): (req, res, next) => void
optimizeResponseForAI(options?: AIOptimizationOptions): (req, res, next) => void

class AIVisitorLogger {
  constructor(config?: LoggerConfig)
  middleware(): (req, res, next) => void
  log(entry: CrawlerLog): void                 // manual logging, non-Express frameworks
  getLogs(filter?: { botName?: string; days?: number; url?: string }): CrawlerLog[]
  getStats(days?: number): Record<string, BotStatsSerialized>
  clearLogs(): void
}

interface LoggerConfig {
  storage?: "file" | "memory" | "both";   // default storage strategy
  logFilePath?: string;                    // default: ./logs/ai-crawler.json
  trackCrawlers?: string[];
  maxMemoryEntries?: number;
}

AIVisitorLogger's file/memorystorage doesn't persist across invocations on serverless platforms like Vercel — see /built-withfor how this site logs crawler visits instead (its own Postgres table, not this class's storage).

ai-visibility/next

Edge-safe. Imports types from next/server (optional peer dependency).

createNextMiddleware(options?: NextMiddlewareOptions): (req: NextRequest) => NextResponse

interface NextMiddlewareOptions {
  additionalBots?: string[];
  ignoreBots?: string[];
  headerName?: string;    // response header marking a detected bot request, default 'x-ai-crawler'
  rewrite?: string | ((bot: BotInfo, req: NextRequest) => string);
  onDetect?: (bot: BotInfo, req: NextRequest) => void;
}

detectAndOptimize(html: string, userAgent: string, options?: DetectAndOptimizeOptions):
  { isBot: boolean; botName: string | null; html: string }

detectAndOptimize() is framework-agnostic — HTML string and User-Agent string in, transformed HTML out, no request/response objects needed.

onDetectis not awaited internally and this function doesn't expose the middleware's waitUntil, so a database write started inside it isn't guaranteed to complete before the edge isolate is reclaimed. This site calls detectBot() from ai-visibility/detector directly instead, to use its own waitUntil — see the App Router recipe.

ContentAnalyzer

Exported from the root barrel. Scores HTML for AI readability.

class ContentAnalyzer {
  constructor(options?: AnalyzerOptions)
  analyze(html: string): Promise<AIReadabilityScore>
}

interface AIReadabilityScore {
  overallScore: number; // 0-100
  breakdown: {
    answerFrontLoading: number;
    factDensity: number;
    headingStructure: number;
    eeatSignals: number;
    snippability: number;
    schemaCoverage: number;
  };
  issues: { type: string; severity: "high" | "medium" | "low"; message: string; fix: string }[];
  recommendations: string[];
}

AIVisitorLogger & Dashboard

Dashboard is exported from the root barrel (not ai-visibility/express — it only renders data you hand it, so it has no Express dependency itself):

class Dashboard {
  constructor()
  getHtml(): string
  formatData(stats, logs): DashboardData
  render(stats, logs, options?: { autoRefresh?: boolean; refreshInterval?: number }): string
  getPath(): string
}

createDashboard(): Dashboard

CLI

Verified by running each command's --help directly against the installed package:

ai-visibility init [--dir <path>] [--site-name <name>] [--site-url <url>] [--block-training]
ai-visibility analyze [--dir <path>] [--file <path>] [--json] [--min-score <n>]
ai-visibility generate robots|llms|schema [options]
ai-visibility logs [--crawler <name>] [--days <n>] [--url <path>] [--summary] [--log-file <path>] [--json]