-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathgenerate-llms.mjs
More file actions
148 lines (131 loc) · 4.82 KB
/
Copy pathgenerate-llms.mjs
File metadata and controls
148 lines (131 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env node
/**
* Generates `docs/llms.txt` and `docs/llms-full.txt` from the docsify content.
*
* - `llms.txt` An index of the documentation following the llmstxt.org
* spec: a title, a short summary and curated sections of
* links (one per page, pointing at the raw `.md` source so
* that an LLM can fetch the plain Markdown).
* - `llms-full.txt` The same pages with their full Markdown content inlined,
* ready to be ingested directly into an LLM context window.
*
* The page list and grouping are derived from `docs/_navbar.md`, so the output
* stays in sync with the navigation that humans see. Run via `npm run llms`.
*/
import { readFile, writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const root = dirname(fileURLToPath(import.meta.url));
const docsDir = join(root, 'docs');
const { homepage, description } = require('./package.json');
const SITE_NAME = 'Livery Video Docs';
const BASE_URL = (homepage || 'https://docs.livery.live').replace(/\/$/, '');
/** Build the absolute URL to a page's raw Markdown source. */
const mdUrl = (path) => `${BASE_URL}/${path.replace(/^\//, '')}`;
/**
* Parse `_navbar.md` into ordered groups of links.
* Top-level `- Group` lines start a section; indented `- [Title](/path 'Desc')`
* lines are the pages within it.
*
* @returns {{ title: string, links: { title: string, path: string, desc: string }[] }[]}
*/
function parseNavbar(markdown) {
const linkRe = /\[([^\]]+)\]\(([^ )]+)(?:\s+['"]([^'"]*)['"])?\)/;
const groups = [];
let current = null;
for (const rawLine of markdown.split('\n')) {
const line = rawLine.replace(/\s+$/, '');
if (!line.trim() || line.trim().startsWith('<!--')) continue;
const indent = line.length - line.trimStart().length;
const match = line.trim().match(/^[-*]\s+(.*)$/);
if (!match) continue;
const content = match[1];
const link = content.match(linkRe);
if (link && indent > 0) {
// A page within the current group.
if (!current) current = { title: 'Documentation', links: [] };
current.links.push({
title: link[1].trim(),
path: link[2].trim(),
desc: (link[3] || '').trim(),
});
} else if (!link) {
// A top-level group heading.
current = { title: content.trim(), links: [] };
groups.push(current);
}
}
return groups.filter((group) => group.links.length > 0);
}
/** Extract a one-line summary: the first real paragraph of the README. */
function summaryFromReadme(markdown) {
for (const block of markdown.split(/\n\s*\n/)) {
const text = block.trim();
if (!text || text.startsWith('#') || text.startsWith('![') || text.startsWith('?>')) {
continue;
}
return text.replace(/\s+/g, ' ').trim();
}
return description || '';
}
/** Render the llms.txt index file. */
function renderIndex(summary, groups) {
const lines = [`# ${SITE_NAME}`, '', `> ${summary}`, ''];
for (const group of groups) {
lines.push(`## ${group.title}`, '');
for (const link of group.links) {
const suffix = link.desc ? `: ${link.desc}` : '';
lines.push(`- [${link.title}](${mdUrl(link.path)})${suffix}`);
}
lines.push('');
}
return `${lines.join('\n').trim()}\n`;
}
/** Render the llms-full.txt file with each page's Markdown inlined. */
async function renderFull(summary, groups) {
const sections = [`# ${SITE_NAME}`, '', `> ${summary}`, ''];
for (const group of groups) {
for (const link of group.links) {
const file = join(docsDir, link.path.replace(/^\//, ''));
let content;
try {
content = (await readFile(file, 'utf8')).trim();
} catch {
console.warn(` ! skipping ${link.path} (not found locally)`);
continue;
}
sections.push(
'---',
'',
`# ${link.title}`,
`Source: ${mdUrl(link.path)}`,
'',
content,
'',
);
}
}
return `${sections.join('\n').trim()}\n`;
}
async function main() {
const [navbar, readme] = await Promise.all([
readFile(join(docsDir, '_navbar.md'), 'utf8'),
readFile(join(docsDir, 'README.md'), 'utf8'),
]);
const groups = parseNavbar(navbar);
const summary = summaryFromReadme(readme);
const pageCount = groups.reduce((n, group) => n + group.links.length, 0);
const index = renderIndex(summary, groups);
const full = await renderFull(summary, groups);
await Promise.all([
writeFile(join(docsDir, 'llms.txt'), index),
writeFile(join(docsDir, 'llms-full.txt'), full),
]);
console.log(`Generated docs/llms.txt and docs/llms-full.txt (${pageCount} pages).`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});