Discovery + readiness manifest emitter so agents can introspect aihu apps.
classifyBotUserAgentfunction classifyBotUserAgent(userAgent: string): BotTier | nullClassify a User-Agent string against the unified bot registry.
createAgentReadinessRoutesfunction createAgentReadinessRoutes(config: AgentReadinessConfig): { readonly llmsTxt: RouteHandler readonly llmsFullTxt: RouteHandler readonly mcpServerCard: RouteHandler readonly robotsTxt: RouteHandler readonly a2aCard: RouteHandler readonly mcpDiscovery: RouteHandler readonly sitemapXml: RouteHandler }Create fetch-API route handlers for all agent-readiness endpoints.
createContentNegotiationHandlerfunction createContentNegotiationHandler(opts: ContentNegotiationOptions): MiddlewarecreateRouteMarkdownResolverfunction createRouteMarkdownResolver( options: RouteMarkdownResolverOptions, ): MarkdownResolvergenerateA2aCardfunction generateA2aCard(config: A2aCardConfig): A2aCardGenerate an A2A Agent Card object.
generateJsonLdfunction generateJsonLd(page: Partial<JsonLdPage>): stringGenerate a JSON-LD object string.
generateLlmsFullTxtfunction generateLlmsFullTxt(config: LlmsTxtConfig): stringgenerateLlmsTxtfunction generateLlmsTxt(config: LlmsTxtConfig): stringgenerateMcpDiscoveryfunction generateMcpDiscovery(config: McpDiscoveryConfig): McpDiscoveryGenerate an MCP Discovery document.
generateMcpServerCardfunction generateMcpServerCard(config: McpServerCardConfig): McpServerCardgenerateRobotsTxtfunction generateRobotsTxt(config: RobotsConfig = {}): stringgenerateSitemapXmlfunction generateSitemapXml(config: SitemapConfig): stringGenerate a sitemap.xml string from a list of page URLs.
isAiCrawlerUserAgentfunction isAiCrawlerUserAgent(userAgent: string): booleanDoes this user-agent belong to an AI crawler?
seoLlmsSectionsfunction seoLlmsSections(config: SeoLlmsSectionsConfig): ReadonlyArray<LlmsTxtSection>Returns an array of LlmsTxtSection entries for composition into the `llmsSections` config field — one section titled after the site, linking each sitemap source path.
skillsFromRegistryfunction skillsFromRegistry( metas: readonly AgentMetadata[] = getAllAgentMetadata(), ): ReadonlyArray<AgentSkill>Derive the full set of MCP skills from the live `@aihu/agent` registry.
viteAgentReadinessIntegrationfunction viteAgentReadinessIntegration(config: AgentReadinessConfig): VitePluginThe `viteAgentReadinessIntegration()` Vite plugin (v0.7.4 canonical name).
agentReadinessconst agentReadinessAI_BOT_LISTconst AI_BOT_LIST: ReadonlyArray<string>All 13 known AI user agents (derived from the registry; order preserved).
AI_TRAINING_CRAWLER_BOTSconst AI_TRAINING_CRAWLER_BOTS: ReadonlyArray<string>Training/scraping crawlers — explicit opt-in; disallowed under `'allow-agents'`.
AI_USER_FETCHER_BOTSconst AI_USER_FETCHER_BOTS: ReadonlyArray<string>User-delegated fetchers / cited-search agents — allowed under `'allow-agents'`.
BOT_REGISTRYconst BOT_REGISTRY: ReadonlyArray<BotEntry>The ONE unified bot registry: every classified crawler across all three tiers.
SEARCH_BOTSconst SEARCH_BOTS: ReadonlyArray<string>Traditional search-index crawler tokens (derived; `searcher` tier only).
RouteMarkdownResolverclass RouteMarkdownResolver implements MarkdownResolverThe production `MarkdownResolver`: projects route declarations plus the agent-component registry into markdown.
A2aCapabilitiesinterface A2aCapabilities {
readonly streaming?: boolean
readonly pushNotifications?: boolean
}A2A (Agent-to-Agent) Agent Card generator.
A2aCardinterface A2aCard {
readonly name: string
readonly description?: string
readonly url: string
readonly version?: string
readonly capabilities: {
readonly streaming: boolean
readonly pushNotifications: boolean
}
readonly skills?: ReadonlyArray<A2aSkill>
}A2aCardConfiginterface A2aCardConfig {
readonly name: string
readonly description?: string
readonly url: string
readonly version?: string
readonly capabilities?: A2aCapabilities
readonly skills?: ReadonlyArray<A2aSkill>
}A2aSkillinterface A2aSkill {
readonly id: string
readonly name: string
readonly description?: string
}AgentSkillinterface AgentSkill {
readonly id: string
readonly name: string
readonly description: string
readonly inputSchema?: Record<string, unknown>
}BotEntryinterface BotEntry {
readonly agent: string
readonly tier: BotTier
/** Operator + what the agent actually is — the per-bot classification record. */
readonly why: string
}One entry in the classified bot registry.
ComponentMetaLikeinterface ComponentMetaLike {
readonly tag: string
readonly describes?: string
readonly state?: Record<string, string>
readonly actions?: Record<string, { readonly returns?: Record<string, { type: string }> }>
/**
* GX Phase 3: the compiled `extract` policy (rides `AgentMetadata`'s open
* index signature). Consulted — fail-closed — to decide whether the
* component is advertised in llms.txt at all. Absent → advertised (the
* resolved default, `read: 'agents'`).
*/
readonly extract?: unknown
}Minimal shape used for the `## Components` section.
ContentNegotiationOptionsinterface ContentNegotiationOptions {
readonly resolver: MarkdownResolver
/**
* Token count estimator for the x-markdown-tokens response header.
* Default: Math.ceil(content.length / 4).
*/
readonly estimateTokens?: (content: string) => number
/**
* Serve markdown to a recognized AI crawler that sent no `Accept` preference.
* Default: true.
*
* Rationale: AI crawlers do not send `Accept: text/markdown` — of 7 agents
* tested only 3 do, and those are coding agents, not search crawlers
* (`docs/domain-hints/seo-and-agent-discoverability.md` §7.5). Without this
* the negotiation path is unreachable for the clients with the volume.
* Precedent: Next.js 15.2+ keeps an `htmlLimitedBots` list and UA-sniffs it.
*
* Set false to require an explicit `Accept` header from every client.
*/
readonly userAgentFallback?: boolean
/**
* Override crawler detection. Defaults to {@link isAiCrawlerUserAgent},
* which matches against the shared `AI_BOT_LIST`.
*/
readonly isAgentUserAgent?: (userAgent: string) => boolean
}JsonLdPageinterface JsonLdPage {
readonly '@context': string // 'https://schema.org'
readonly '@type': string // 'WebPage', 'Article', etc.
readonly name?: string
readonly description?: string
readonly url?: string
[key: string]: unknown
}A schema.org page object.
LlmsTxtConfiginterface LlmsTxtConfig {
readonly name: string
readonly summary?: string
readonly sections: ReadonlyArray<LlmsTxtSection>
readonly optional?: ReadonlyArray<LlmsTxtLink>
/**
* Component metadata, rendered as a `## Components` section listing each
* component's tag, description, callable actions, and readable state.
* When empty (zero components) the section is omitted entirely.
*
* GX Phase 3 (#437-GX): the section is FILTERED by each component's
* compiled `extract` policy — a component whose `read` excludes
* user-directed AI fetchers (`'search'`/`'none'`/any hard value) or whose
* `call` is closed (`'none'`) is not advertised here (spec §8). Derived,
* fail-closed; components without an `extract` member (the pre-GX registry
* shape) are advertised exactly as before.
*/
readonly components?: ReadonlyArray<ComponentMetaLike>
/**
* GX Phase 3 (#437-GX): the compiled route table (structurally,
* `@aihu/router` `RouteDefinition[]`). Rendered as a derived `## Routes`
* section listing exactly the routes whose declared `read:` admits
* user-directed AI fetchers (`'all'`/`'agents'`) — `'search'`/`'none'` and
* every hard value are absent from this agent-facing document (spec §8).
* One source (the compiled declarations), no hand-maintained route list.
* Omitted → no `## Routes` section (byte-identical to pre-GX output).
*/
readonly routes?: ReadonlyArray<RouteReadPolicy>
/**
* Base URL for derived route links (e.g. `https://example.com`, no trailing
* slash). Without it, derived route entries render as bare patterns.
*/
readonly baseUrl?: string
}LlmsTxtLinkinterface LlmsTxtLink {
readonly title: string
readonly url: string
readonly description?: string
}LlmsTxtSectioninterface LlmsTxtSection {
readonly title: string
readonly links: ReadonlyArray<LlmsTxtLink>
}MarkdownResolverinterface MarkdownResolver {
/**
* Return markdown content for the given URL path, or null when none exists.
* Implementations must catch errors internally and return null rather than throw.
*/
resolve(path: string): Promise<string | null>
}Abstract interface for resolving markdown content from a URL path.
McpAuthConfiginterface McpAuthConfig {
readonly type: 'oauth2'
readonly authorizationUrl: string
readonly tokenUrl: string
readonly scopes?: ReadonlyArray<string>
/**
* Resource identifier URI (RFC 9728 §2).
* Defaults to the `endpoint` URL when not provided.
*/
readonly resourceUri?: string
}OAuth 2.0 auth configuration for a protected MCP endpoint.
McpDiscoveryinterface McpDiscovery {
readonly mcpServers: Record<string, McpDiscoveryServer>
}McpDiscoveryConfiginterface McpDiscoveryConfig {
readonly name: string
readonly url: string
readonly description?: string
}McpDiscoveryServerinterface McpDiscoveryServer {
readonly url: string
readonly name: string
readonly description?: string
}MCP Discovery document generator.
McpServerCardinterface McpServerCard {
readonly $schema: 'https://modelcontextprotocol.io/schemas/server-card/v1.0'
readonly version: '1.0'
readonly protocolVersion: string
readonly serverInfo: {
readonly name: string
readonly version: string
readonly description?: string
readonly homepage?: string
}
readonly transport: {
readonly type: 'streamable-http' | 'sse'
readonly url: string
}
readonly capabilities: {
readonly tools: boolean
readonly resources: boolean
readonly prompts: boolean
}
readonly tools?: ReadonlyArray<{
readonly name: string
readonly description: string
}>
readonly auth?: {
readonly type: 'oauth2'
/**
* OAuth 2.0 authorization-server issuer identifier (the origin of the
* configured token endpoint). A consumer performs RFC 8414 metadata
* discovery against the issuer itself — we do NOT synthesize or advertise a
* `/.well-known/oauth-authorization-server` URL here: that document belongs
* on the authorization server, not on this MCP server, and we don't serve it.
*/
readonly authorizationServer: string
}
}MCP Server Card output object.
McpServerCardConfiginterface McpServerCardConfig {
readonly name: string
readonly version: string
readonly endpoint: string
readonly skills?: ReadonlyArray<AgentSkill>
readonly auth?: McpAuthConfig
readonly description?: string
readonly homepage?: string
/** Default: '2025-06-18'. */
readonly protocolVersion?: string
/** Default: 'streamable-http'. */
readonly transportType?: 'streamable-http' | 'sse'
}RobotsConfiginterface RobotsConfig {
/**
* AI-agent crawl policy. Default: `'allow-agents'` (tiered) — user-delegated
* fetchers get `Allow: /`, training/scraping crawlers get `Disallow: /`.
* `'allow-all'` and `'deny-all'` apply one directive to all 13 bots.
* An array of rules gives per-bot control.
*/
readonly aiAgents?: AiAgentsPolicy | ReadonlyArray<RobotsRule>
readonly standard?: ReadonlyArray<RobotsRule>
/**
* GX Phase 3 (#437-GX): the compiled route table (structurally, `@aihu/router`
* `RouteDefinition[]` — e.g. the `virtual:aihu-routes` module). Each route's
* compiled `extract.read` declaration derives per-path directives over the
* bot-registry tiers (see {@link deriveRouteDirectives} for the mapping).
* Omitted, or with every route at the recorded default (`read: 'agents'`),
* output is byte-identical to the #430 tiered default. COMPLIANCE-TIER:
* advisory for compliant crawlers; the origin gate is the authority.
*/
readonly routes?: ReadonlyArray<RouteReadPolicy>
readonly sitemap?: string
/**
* The trailing `User-agent: * / Allow: /` block is ALWAYS emitted for
* predictable output — set `wildcard: false` to suppress it. It is also
* skipped automatically when one of your own rules already targets `*`
* (your rule IS the wildcard decision; emitting a second block would
* contradict it).
*/
readonly wildcard?: boolean
}RobotsRuleinterface RobotsRule {
readonly userAgent: string | ReadonlyArray<string>
readonly allow?: ReadonlyArray<string>
readonly disallow?: ReadonlyArray<string>
readonly crawlDelay?: number
}RouteMarkdownEntryinterface RouteMarkdownEntry {
/** URL path this entry answers for, e.g. `/about`. Leading slash required. */
readonly path: string
/** Derived from the route's `@route { head }` title. */
readonly title?: string
/** Derived from the route's `@route { head }` description/meta. */
readonly description?: string
/** Primary prose content for the route, already markdown. */
readonly body?: string
/**
* Tags of the agent-annotated components mounted on this route. Each is
* expanded from the live registry into its callable actions and readable
* state — a surface that NEVER appears in the rendered HTML, and therefore
* cannot be recovered by any HTML→markdown converter.
*/
readonly components?: ReadonlyArray<string>
}A route's agent-facing declaration: the same source that builds the UI, projected into the shape an agent asked for.
RouteMarkdownResolverOptionsinterface RouteMarkdownResolverOptions {
readonly routes: ReadonlyArray<RouteMarkdownEntry>
/** Site name, used as the document title when a route declares none. */
readonly siteName?: string
/**
* Component-registry reader. Defaults to `getAllAgentMetadata` from
* `@aihu/agent`. Injectable so the resolver stays testable without a
* populated global registry.
*/
readonly readComponents?: () => ReadonlyArray<ComponentMetaLike>
}RouteReadPolicyinterface RouteReadPolicy {
/** Route pattern, e.g. `/pricing` or `/reports/:id`. */
readonly pattern: string
/** The compiled `extract` object; only `read` is consulted here. */
readonly extract?: { readonly read?: unknown } | undefined
}One compiled route's crawl policy — the `robots` projection of a `RouteDefinition` (`@aihu/router`) or `.route.json` sidecar.
SeoLlmsSectionsConfiginterface SeoLlmsSectionsConfig {
readonly siteName: string
readonly baseUrl: string // e.g. 'https://example.com' (no trailing slash)
readonly sitemapSources?: ReadonlyArray<{ readonly path: string }>
}SitemapConfiginterface SitemapConfig {
readonly pages: ReadonlyArray<SitemapUrl>
}SitemapUrlinterface SitemapUrl {
readonly url: string
readonly lastmod?: string
readonly changefreq?: SitemapChangefreq
readonly priority?: number
}AgentReadinessConfigtype AgentReadinessConfigBotTiertype BotTier = 'searcher' | 'user-fetcher' | 'training-crawler'Crawler tier — the classification the GX `read:` axis consumes.
SitemapChangefreqtype SitemapChangefreq =
| 'always'
| 'hourly'
| 'daily'
| 'weekly'
| 'monthly'
| 'yearly'
| 'never'XML Sitemap generator.