registry:file · Docs Tooling
llms-txt — the AI export pass
jixoai sites are AI-friendly by design, so the scaffold ships the export natively: after the final static build, one pass scans the published HTML and writes llms.txt, llms-full.txt and a .md mirror beside every page. No routing takeover — the generator consumes the artifact agents will actually fetch.
llms-txt
One deterministic pass over the final dist: extract each page's main content, whitelist-convert it to Markdown, mirror it beside the HTML, and compose the index (plus the single-file llms-full.txt).
pipeline · no routing takeover
What one build pass does
The generator never touches the routing layer or the source tree — it reads what the site actually published.
final dist/ outputs (same tree)
├── index.html ─┐ ├── index.md
├── components/*.html ├─ extract ├── components/*.md
│ <main> → markdown │ ├── llms.txt (index, absolute URLs)
│ strip nav/forms │ └── llms-full.txt (cap-guarded)
└── en/… zh/… (i18n) ─┘ └── en/llms.txt … (locale split) - content root — each page's
<main>(body fallback); chrome stripped by platform semantics only: nav/forms/scripts,buttoncontrols,aria-hiddendecoration,inertcollapsed panels - whitelist converter — a stack-based tokenizer, not regex chains: headings, lists, tables (colspan degrades instead of lying), fenced code with language detection, entity decoding, and dangerous links dropped
- agent-first links — the index links each page's .md mirror by absolute URL;
linkStyle: 'root-relative'exists for preview builds - machine-readable registry — this site's own index also points agents at
/r/registry.jsonunder an Optional section
/* the installed generator (npx jixoai-ui add llms-txt) */wiring
Two shapes, one core
generateLlmsTxt(distDir, config) is the only implementation. The vite adapter exists for plain-build sites; orchestrated sites call the core directly as their last step.
Plain vite build sites (the openspecui pattern) use
the plugin — it runs exactly once, in the SSR build's closeBundle, after SvelteKit's
adapter has written the final dist:
// vite.config.ts — plain vite build sites (one plugin line)
import { sveltekit } from '@sveltejs/kit/vite';
import { llmsTxt } from './vite-plugins/llms-txt.mjs';
export default {
plugins: [
sveltekit(),
llmsTxt({
distDir: 'dist',
siteUrl: 'https://myproject.jixoai.com',
title: 'myproject',
summary: 'What this site is, for agents.',
}),
],
};Orchestrated sites (the unipty pattern — build scripts that inject artifacts after vite) call the core on the final output:
// scripts/build.mjs — orchestrated sites call the core directly,
// as the LAST step, on the FINAL output directory:
import { generateLlmsTxt } from './vite-plugins/llms-txt.mjs';
// …vite build, artifact copies, CNAME gating all done…
const report = generateLlmsTxt(distDir, {
siteUrl: 'https://myproject.jixoai.com',
title: 'myproject',
summary: 'What this site is, for agents.',
exclude: ['404.html'],
full: { enabled: true, maxBytes: 10_000_000 },
});
console.log('llms-txt:', report.pages, 'pages,', report.files.length, 'files');law
The laws
The generator is a guest in your dist: it only touches what it declares, it never guesses, and it fails loudly rather than shipping something subtly wrong.
- scan the final artifact — what agents fetch is what got published, not what the source tree promised
- declared outputs only — llms.txt, llms-full.txt, and .md mirrors; nothing is deleted, robots.txt and sitemap are never touched
- hand-written .md is sacred — every generated mirror starts with a provenance marker; a conflicting file aborts the run
- byte-deterministic — same dist in, same bytes out; re-runs converge, never duplicate
- llms-full.txt is capped — over full.maxBytes the run fails naming the size; no silent truncation