Cómo construimos @dualnova/llms-txt: un validator zero-deps para el standard de AI crawlers
Deep dive sobre @dualnova/llms-txt — la librería TypeScript + CLI para parsear, validar y generar archivos /llms.txt, el standard emergente para guiar a los crawlers de AI search.
Cómo construimos @dualnova/llms-txt: un validator zero-deps para el standard de AI crawlers
TL;DR: /llms.txt es a la búsqueda con AI lo que robots.txt fue a Google en los 90 — un archivo pequeño y well-known en la raíz del dominio que le da a los crawlers un mapa curado de qué importa. Publicamos @dualnova/llms-txt, una librería TypeScript + CLI para parsear, validar y generar estos archivos. Cero dependencias en runtime, ESM-only, MIT. Es el mismo código que genera el archivo que puedes ver hoy en dualnova.org/llms.txt. Este post es la versión larga de por qué la construimos, qué tiene adentro, y la realidad honesta de cuánto mueve la aguja hoy.
Qué es /llms.txt, en 30 segundos
/llms.txt es una convención propuesta en llmstxt.org para publicar un resumen Markdown del sitio en la ruta well-known /llms.txt. El formato es intencionalmente diminuto:
# Título del sitio
> Una sola oración describiendo qué es el sitio.
## Heading de sección
- [Título de la página](url): descripción opcional
- [Otra página](url)
## Otra sección
- [Más páginas](url)
Eso es todo el spec. No hay JSON Schema, no XML, no campos requeridos más allá del H1. El objetivo es ser el artefacto más pequeño posible que un LLM pueda ingerir y entender inmediatamente de qué se trata el sitio y qué páginas leer para más detalle.
La motivación: los AI crawlers no tienen la paciencia que tiene el renderer de Google. Quieren señal, no ruido. Un /llms.txt de 50 líneas le es más útil a un bot de Perplexity en tres segundos que treinta HTML cacheados llenos de nav, footer y modales.
Por qué publicarlo si ningún motor AI lo usa oficialmente todavía
Esta es la parte que la mayoría de los posts se saltan. Seamos honestos: a mayo 2026, fuentes primarias de Google (John Mueller en sept 2024, Gary Illyes en oct 2024), el estudio de SE Ranking con 300,000 dominios, y la auditoría de logs de OtterlyAI reportan todos que ningún motor AI mayor usa hoy /llms.txt como señal de ranking para citaciones. Los crawlers ni siquiera lo solicitan consistentemente.
Entonces, ¿para qué molestarse?
- El costo es casi cero. Un archivo estático de 100 líneas que escribes una vez y regeneras desde un build script.
- El standard puede ser adoptado. Anthropic, OpenAI y Perplexity han reconocido por separado que la convención existe. Si alguno empieza a usarla, quieres ya tener el archivo.
- Te fuerza a tener claridad. Escribir un resumen de una página de tu negocio en 200 palabras es un ejercicio útil sin importar quién lo lea. Varias de las decisiones de copy más claras de nuestro equipo arrancaron dentro de su propio
/llms.txt. - Otras herramientas ya lo ingieren. La auditoría "Agent Ready" de Cloudflare lo lee. Algunas herramientas de Brand Radar lo indexan. Harnesses de agentes independientes lo usan como path de descubrimiento por defecto.
Lo que /llms.txt no hace: saltarse el trabajo de ser citable. Las palancas grandes para citaciones AI siguen siendo menciones de marca en YouTube, Reddit, Wikipedia y GitHub — el estudio de Ahrefs de diciembre 2025 con 75,000 marcas encontró que estas correlacionan tres veces más fuerte con citaciones AI que los backlinks, y /llms.txt no correlacionó con nada medible.
Si un vendor te lo vende como la palanca para visibilidad AI, salte de ahí. Es higiene, no estrategia.
Qué tiene la librería
El paquete de npm exporta tres funciones y trae un CLI.
parseLlmsTxt(source: string): ParsedLlmsTxt
Un parser Markdown tolerante que retorna secciones y links tipados. No tira excepciones por campos faltantes — recibes lo que estaba presente.
import { parseLlmsTxt } from '@dualnova/llms-txt';
const { title, description, sections } = parseLlmsTxt(source);
for (const section of sections) {
console.log(`## ${section.heading} — ${section.links.length} links`);
}
validateLlmsTxt(source: string): ValidationResult
Surface issues con niveles de severidad (error, warning, info). Los errors fallan la validación; warnings e info no.
import { validateLlmsTxt } from '@dualnova/llms-txt';
const { valid, issues } = validateLlmsTxt(source);
if (!valid) {
for (const issue of issues.filter((i) => i.severity === 'error')) {
console.error(`✗ ${issue.message}${issue.at ? ` (${issue.at})` : ''}`);
}
process.exit(1);
}
Cosas que detecta:
- H1 title faltante → error
- Blockquote
> descriptionfaltante → warning - Description más corta que 40 chars → info
- Secciones vacías → warning
- URLs inválidas (no
http(s)://...y no site-relativas/...) → error - Cero secciones H2 → warning
El check completo corre en microsegundos — no hay paso markdown-to-AST, solo regex cuidadosa línea por línea.
buildLlmsTxt(options: BuildOptions): string
Genera un /llms.txt well-formed desde un objeto tipado. La función existe para que la cables a tu build pipeline y dejes de editar el archivo a mano.
import { buildLlmsTxt } from '@dualnova/llms-txt';
import { writeFileSync } from 'node:fs';
const md = buildLlmsTxt({
title: 'Acme',
description: 'Acme construye robots autónomos para warehouse.',
sections: [
{
heading: 'Productos',
links: [
{ title: 'ARO-100', url: 'https://acme.example/products/aro-100', description: 'AMR mediano' },
{ title: 'ARO-200', url: 'https://acme.example/products/aro-200' },
],
},
],
});
writeFileSync('./public/llms.txt', md);
El output es idéntico a lo que parseLlmsTxt consumiría — hacemos round-trip del formato en nuestra suite de tests para garantizar compatibilidad.
CLI
# Valida el archivo en el directorio actual
llms-txt validate
# Valida un archivo local específico
llms-txt validate ./public/llms.txt
# Fetch + valida uno remoto (handy para checks de CI contra producción)
llms-txt validate --url https://dualnova.org/llms.txt
Output de ejemplo:
Validating /home/me/site/public/llms.txt
Title: DualNova
Description: DualNova is a blockchain and AI software development company …
Sections: 5
Links: 18
WARN Section "Recent client work" has no links and no content. (sections[3])
✓ 0 error(s), 1 warning(s)
Exit codes: 0 limpio, 1 errores de validación, 2 no pudo leer el archivo. CI-ready.
Usándolo en un build de Next.js
El patrón que usamos en dualnova.org. Añade un route en app/llms.txt/route.ts:
import { buildLlmsTxt } from '@dualnova/llms-txt';
export function GET() {
const body = buildLlmsTxt({
title: 'Acme',
description: 'Acme construye robots autónomos usados en 40+ centros de distribución.',
sections: [
{
heading: 'Qué hace Acme',
freeform:
'Robots móviles autónomos (AMRs) para warehouse pick-pack-ship. Software de orquestación de flota. SDK open source.',
},
{
heading: 'Páginas clave',
links: [
{ title: 'Home', url: 'https://acme.example/' },
{ title: 'Productos', url: 'https://acme.example/products' },
{ title: 'Clientes', url: 'https://acme.example/customers' },
],
},
],
});
return new Response(body, {
headers: { 'content-type': 'text/plain; charset=utf-8' },
});
}
export const dynamic = 'force-static';
Dos cosas que esto te da sobre editar el archivo estático a mano:
- Single source of truth. Si ya tienes tus páginas listadas en un sitemap o frontmatter MDX, pipea eso al call y deja de driftear.
- CI rompe si publicas un archivo malformed. El validator corre en build time.
GitHub Action: que falle el build con /llms.txt malformed
# .github/workflows/llms-txt.yml
name: Validate llms.txt
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npx -y @dualnova/llms-txt validate ./public/llms.txt
Tres líneas de config y tu /llms.txt es ahora parte de tu CI gate, igual que tu compiler de TypeScript.
Cómo está construida (los detalles aburridos)
Cero dependencias en runtime. La librería entera son cuatro archivos bajo src/:
src/
├── index.ts ← re-exports del API público
├── parser.ts ← parser Markdown driven por regex
├── validator.ts ← reglas + clasificación de severidad
├── builder.ts ← serializer objeto tipado → Markdown
└── cli.ts ← argv parser + output coloreado
Tested con el test runner built-in de Node (sin jest, sin vitest), lo que mantiene el install size diminuto. Ships ESM-only ya que Node 20+ ha sido estable en import.meta y top-level await por más de un año.
El parser deliberadamente acepta input malformed y emite warnings estructurados en vez de tirar excepciones — el validator es el único lugar que decide si algo es fatal. Esta separación importa cuando usas la librería dentro de un CMS que permite drafts parciales.
Dónde encaja en el stack GEO
/llms.txt es la segunda cosa más barata que puedes hacer para Generative Engine Optimization. La más barata es desbloquear los AI crawlers en robots.txt (y parar que Cloudflare los over-bloquee). Después, en orden aproximado de return sobre esfuerzo:
- Desbloquear los AI crawlers en
robots.txty Cloudflare AI Crawl Control. - Publicar
/llms.txtcon esta librería. - Añadir un
@graphSchema.org multi-entity con Organization + WebSite + ProfessionalService. - Publicar pasajes citables de 134–167 palabras en cada página clave (el patrón "X es [definición]").
- Construir menciones de marca en YouTube, Reddit, GitHub, Wikipedia — por lejos la palanca más grande.
La librería maneja el paso 2. Los pasos 1, 3 y 4 son cambios de código en tu repo. El paso 5 son meses de trabajo paciente de marca.
Instalar y probar
npm install @dualnova/llms-txt
# o corre el CLI sin instalar
npx @dualnova/llms-txt validate --url https://dualnova.org/llms.txt
Si encuentras un bug, abre un issue. Si la usas en tu stack, deja una nota en la pestaña de discussions — estamos coleccionando reference deployments para el README.
- GitHub: github.com/DualNova/llms-txt
- npm: @dualnova/llms-txt
- Librerías hermanas del mismo release: @dualnova/agent-skills y tokenization-templates
Construido por DualNova — desarrollo de software blockchain y AI para LATAM y Estados Unidos. Equipo bilingüe en Caracas, Bogotá y Miami. Si estás enviando un producto blockchain o de AI agents y quieres una llamada técnica de 30 minutos, agéndala aquí.