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, BytespiderThe 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:
<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(): DashboardCLI
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]