@pointsharp/antora-llm-generator 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -5
- package/llm-generator.js +35 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,10 +11,8 @@ Both files help large-language models ingest your documentation with proper stru
|
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
|
-
This extension is used locally. Ensure the required dependencies are installed:
|
|
15
|
-
|
|
16
14
|
```bash
|
|
17
|
-
npm install
|
|
15
|
+
npm install @pointsharp/antora-llm-generator
|
|
18
16
|
```
|
|
19
17
|
|
|
20
18
|
---
|
|
@@ -26,7 +24,7 @@ Add the extension to your `antora-playbook.yml`:
|
|
|
26
24
|
```yaml
|
|
27
25
|
antora:
|
|
28
26
|
extensions:
|
|
29
|
-
- require:
|
|
27
|
+
- require: "@pointsharp/antora-llm-generator"
|
|
30
28
|
summary: "Brief summary about your documentation site"
|
|
31
29
|
details: |
|
|
32
30
|
Optional longer description or important notes.
|
|
@@ -40,6 +38,7 @@ antora:
|
|
|
40
38
|
- **`summary`** - Optional. Appears as a blockquote at the top of both output files (following llmstxt.org spec).
|
|
41
39
|
- **`details`** - Optional. Appears as regular text after the summary. Can be multi-line markdown.
|
|
42
40
|
- **`skippaths`** - Optional. Array of glob patterns. Files matching these patterns are omitted from both output files.
|
|
41
|
+
- **`debug`** - Optional. Set to `true` to enable verbose logging during build. Default: `false`.
|
|
43
42
|
|
|
44
43
|
### Navigation-based organization (default)
|
|
45
44
|
|
|
@@ -182,6 +181,40 @@ The duplicate filenames ensure compatibility with different naming conventions.
|
|
|
182
181
|
|
|
183
182
|
---
|
|
184
183
|
|
|
184
|
+
## Logging
|
|
185
|
+
|
|
186
|
+
The extension runs **silently** by default, showing only a success message when complete:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
Generated llms.txt and llms-full.txt
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Enabling verbose output
|
|
193
|
+
|
|
194
|
+
To see detailed processing information (pages collected, components processed, etc.), enable the `debug` flag in your playbook:
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
197
|
+
antora:
|
|
198
|
+
extensions:
|
|
199
|
+
- require: "@pointsharp/antora-llm-generator"
|
|
200
|
+
debug: true
|
|
201
|
+
summary: "Your summary"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
With `debug: true`, you'll see:
|
|
205
|
+
- Navigation data source detection
|
|
206
|
+
- Number of pages in the page map
|
|
207
|
+
- Components and sections being processed
|
|
208
|
+
- Item counts for each section
|
|
209
|
+
|
|
210
|
+
### Error handling
|
|
211
|
+
|
|
212
|
+
The extension always reports errors and critical warnings, regardless of the debug setting:
|
|
213
|
+
- **Errors** - Processing failures or file write errors
|
|
214
|
+
- **Warnings** - Navigator data parsing issues
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
185
218
|
## Example configuration
|
|
186
219
|
|
|
187
220
|
Here's a complete example following the [llmstxt.org](https://llmstxt.org/) best practices:
|
|
@@ -189,7 +222,7 @@ Here's a complete example following the [llmstxt.org](https://llmstxt.org/) best
|
|
|
189
222
|
```yaml
|
|
190
223
|
antora:
|
|
191
224
|
extensions:
|
|
192
|
-
- require:
|
|
225
|
+
- require: "@pointsharp/antora-llm-generator"
|
|
193
226
|
summary: "Comprehensive documentation for the Acme API, including authentication, endpoints, and best practices."
|
|
194
227
|
details: |
|
|
195
228
|
Important notes:
|
|
@@ -224,3 +257,12 @@ Following [llmstxt.org guidelines](https://llmstxt.org/):
|
|
|
224
257
|
This extension follows the official [llmstxt.org specification](https://llmstxt.org/) for maximum compatibility with LLM tools and agents.
|
|
225
258
|
|
|
226
259
|
For more details about the specification, visit [https://llmstxt.org/](https://llmstxt.org/).
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Copyright and License
|
|
264
|
+
|
|
265
|
+
Copyright © 2006-present Pointsharp AB.
|
|
266
|
+
|
|
267
|
+
Use of this software is granted under the terms of the Apache License Version 2.0 (Apache-2.0).
|
|
268
|
+
See the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) for the full license text.
|
package/llm-generator.js
CHANGED
|
@@ -34,8 +34,7 @@ module.exports.register = function (context, { config }) {
|
|
|
34
34
|
const skipPaths = config.skippaths || [];
|
|
35
35
|
const summary = config.summary || null;
|
|
36
36
|
const details = config.details || null;
|
|
37
|
-
|
|
38
|
-
logger.info(`Skip paths: ${JSON.stringify(skipPaths)}`);
|
|
37
|
+
const debug = config.debug !== undefined ? config.debug : false;
|
|
39
38
|
|
|
40
39
|
// Helper: Check if a page path matches any skip pattern
|
|
41
40
|
const shouldSkipPath = (path) => {
|
|
@@ -43,7 +42,7 @@ module.exports.register = function (context, { config }) {
|
|
|
43
42
|
};
|
|
44
43
|
|
|
45
44
|
context.on("beforePublish", ({ contentCatalog, siteCatalog }) => {
|
|
46
|
-
logger.info("Assembling content for LLM text files using Antora navigation.");
|
|
45
|
+
if (debug) logger.info("Assembling content for LLM text files using Antora navigation.");
|
|
47
46
|
|
|
48
47
|
// =============================================================================
|
|
49
48
|
// STEP 1: Detect navigation source
|
|
@@ -51,13 +50,15 @@ module.exports.register = function (context, { config }) {
|
|
|
51
50
|
// Two navigation sources are supported:
|
|
52
51
|
// 1. Antora native navigation (from nav.adoc files)
|
|
53
52
|
// 2. Navigator extension (if installed, creates site-navigation-data.js)
|
|
54
|
-
const navDataFile = siteCatalog.getFiles().find(f => f.out?.path
|
|
53
|
+
const navDataFile = siteCatalog.getFiles().find(f => f.out?.path?.endsWith('site-navigation-data.js'));
|
|
55
54
|
const useNavigatorData = !!navDataFile;
|
|
56
55
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
if (debug) {
|
|
57
|
+
if (useNavigatorData) {
|
|
58
|
+
logger.info("Found site-navigation-data.js - will use navigator extension data");
|
|
59
|
+
} else {
|
|
60
|
+
logger.info("Navigator extension not detected - will use Antora native navigation");
|
|
61
|
+
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// =============================================================================
|
|
@@ -98,20 +99,20 @@ module.exports.register = function (context, { config }) {
|
|
|
98
99
|
|
|
99
100
|
// Skip pages matching skippath patterns from config
|
|
100
101
|
if (shouldSkipPath(page.out.path)) {
|
|
101
|
-
logger.info(`Skipping page matching skip pattern: ${page.out.path}`);
|
|
102
|
+
if (debug) logger.info(`Skipping page matching skip pattern: ${page.out.path}`);
|
|
102
103
|
continue;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
// Skip pages with :page-llms-ignore: attribute
|
|
106
107
|
if (page.asciidoc.attributes["page-llms-ignore"]) {
|
|
107
|
-
logger.info(`Skipping page with 'page-llms-ignore' attribute: ${page.src.path}`);
|
|
108
|
+
if (debug) logger.info(`Skipping page with 'page-llms-ignore' attribute: ${page.src.path}`);
|
|
108
109
|
continue;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
pagesByPath.set(page.out.path, page);
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
logger.info(`Built pagesByPath map with ${pagesByPath.size} pages`);
|
|
115
|
+
if (debug) logger.info(`Built pagesByPath map with ${pagesByPath.size} pages`);
|
|
115
116
|
|
|
116
117
|
// =============================================================================
|
|
117
118
|
// STEP 4: Build navigation sections
|
|
@@ -120,18 +121,16 @@ module.exports.register = function (context, { config }) {
|
|
|
120
121
|
// Each item is a markdown string: "- [Page Title](url): Optional description"
|
|
121
122
|
let componentSections;
|
|
122
123
|
if (useNavigatorData) {
|
|
123
|
-
|
|
124
|
-
logger.info("Using navigator extension data");
|
|
125
|
-
componentSections = buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger);
|
|
124
|
+
componentSections = buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger, debug);
|
|
126
125
|
} else {
|
|
127
|
-
|
|
128
|
-
logger.info("Using Antora native navigation");
|
|
129
|
-
componentSections = buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger);
|
|
126
|
+
componentSections = buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger, debug);
|
|
130
127
|
}
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
if (debug) {
|
|
130
|
+
logger.info(`Component sections map has ${componentSections.size} sections`);
|
|
131
|
+
for (const [name, items] of componentSections.entries()) {
|
|
132
|
+
logger.info(`Section "${name}" has ${items.length} items`);
|
|
133
|
+
}
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
// =============================================================================
|
|
@@ -154,7 +153,7 @@ module.exports.register = function (context, { config }) {
|
|
|
154
153
|
for (const [path, page] of pagesByPath.entries()) {
|
|
155
154
|
// Skip pages with :page-llms-full-ignore: attribute
|
|
156
155
|
if (page.asciidoc.attributes["page-llms-full-ignore"]) {
|
|
157
|
-
logger.info(`Skipping page from full content with 'page-llms-full-ignore' attribute: ${page.src.path}`);
|
|
156
|
+
if (debug) logger.info(`Skipping page from full content with 'page-llms-full-ignore' attribute: ${page.src.path}`);
|
|
158
157
|
continue;
|
|
159
158
|
}
|
|
160
159
|
|
|
@@ -209,7 +208,7 @@ module.exports.register = function (context, { config }) {
|
|
|
209
208
|
contents: Buffer.from(UTF8_BOM + indexContent, 'utf8'),
|
|
210
209
|
});
|
|
211
210
|
|
|
212
|
-
logger.info("llms.txt and llms-full.txt
|
|
211
|
+
logger.info("Generated llms.txt and llms-full.txt");
|
|
213
212
|
});
|
|
214
213
|
};
|
|
215
214
|
|
|
@@ -223,9 +222,10 @@ module.exports.register = function (context, { config }) {
|
|
|
223
222
|
* @param {string} siteUrl - Base URL of the site
|
|
224
223
|
* @param {Map} pagesByPath - Map of page.out.path -> page object
|
|
225
224
|
* @param {Object} logger - Antora logger instance
|
|
225
|
+
* @param {boolean} debug - Enable detailed logging
|
|
226
226
|
* @returns {Map} Map of sectionName -> array of formatted navigation items
|
|
227
227
|
*/
|
|
228
|
-
function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger) {
|
|
228
|
+
function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger, debug) {
|
|
229
229
|
const componentSections = new Map();
|
|
230
230
|
|
|
231
231
|
try {
|
|
@@ -240,7 +240,7 @@ function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logge
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
const siteNavigationData = JSON.parse(jsonMatch[1]);
|
|
243
|
-
logger.info(`Parsed ${siteNavigationData.length} components from navigator data`);
|
|
243
|
+
if (debug) logger.info(`Parsed ${siteNavigationData.length} components from navigator data`);
|
|
244
244
|
|
|
245
245
|
// Process each component
|
|
246
246
|
for (const component of siteNavigationData) {
|
|
@@ -259,8 +259,8 @@ function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logge
|
|
|
259
259
|
for (const set of version.sets || []) {
|
|
260
260
|
if (set.items && set.items.length > 0) {
|
|
261
261
|
// Recursively collect navigation items
|
|
262
|
-
const counts = collectNavItems(set.items, sectionArray, siteUrl, pagesByPath
|
|
263
|
-
logger.
|
|
262
|
+
const counts = collectNavItems(set.items, sectionArray, siteUrl, pagesByPath);
|
|
263
|
+
if (debug) logger.info(`Component ${component.name}: collected ${counts.found} items`);
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
}
|
|
@@ -287,9 +287,10 @@ function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logge
|
|
|
287
287
|
* @param {string} siteUrl - Base URL of the site
|
|
288
288
|
* @param {Map} pagesByPath - Map of page.out.path -> page object
|
|
289
289
|
* @param {Object} logger - Antora logger instance
|
|
290
|
+
* @param {boolean} debug - Enable detailed logging
|
|
290
291
|
* @returns {Map} Map of sectionName -> array of formatted navigation items
|
|
291
292
|
*/
|
|
292
|
-
function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger) {
|
|
293
|
+
function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger, debug) {
|
|
293
294
|
const componentSections = new Map();
|
|
294
295
|
const components = contentCatalog.getComponents();
|
|
295
296
|
|
|
@@ -299,12 +300,12 @@ function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath,
|
|
|
299
300
|
const componentTitle = version.title || componentName;
|
|
300
301
|
const versionString = version.version;
|
|
301
302
|
|
|
302
|
-
logger.info(`Processing component: ${componentName} (${versionString || 'unversioned'})`);
|
|
303
|
+
if (debug) logger.info(`Processing component: ${componentName} (${versionString || 'unversioned'})`);
|
|
303
304
|
|
|
304
305
|
// Check if version has parsed navigation tree
|
|
305
306
|
// (version.navigation is populated by Antora after parsing nav.adoc files)
|
|
306
307
|
if (!version.navigation || !Array.isArray(version.navigation) || version.navigation.length === 0) {
|
|
307
|
-
logger.
|
|
308
|
+
if (debug) logger.info(`No navigation tree found for ${componentName} ${versionString || 'unversioned'}`);
|
|
308
309
|
continue;
|
|
309
310
|
}
|
|
310
311
|
|
|
@@ -323,10 +324,9 @@ function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath,
|
|
|
323
324
|
navTree.items,
|
|
324
325
|
sectionArray,
|
|
325
326
|
siteUrl,
|
|
326
|
-
pagesByPath
|
|
327
|
-
logger
|
|
327
|
+
pagesByPath
|
|
328
328
|
);
|
|
329
|
-
logger.info(`Section "${sectionName}": collected ${counts.found} pages from navigation`);
|
|
329
|
+
if (debug) logger.info(`Section "${sectionName}": collected ${counts.found} pages from navigation`);
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
332
|
}
|
|
@@ -373,8 +373,8 @@ function convertRelativeLinksToAbsolute(markdown, baseUrl, logger) {
|
|
|
373
373
|
const absoluteUrl = new URL(url, baseWithoutFragment).href;
|
|
374
374
|
return `[${text}](${absoluteUrl})`;
|
|
375
375
|
} catch (error) {
|
|
376
|
-
|
|
377
|
-
return match;
|
|
376
|
+
// Return original if resolution fails
|
|
377
|
+
return match;
|
|
378
378
|
}
|
|
379
379
|
});
|
|
380
380
|
}
|
|
@@ -400,7 +400,7 @@ function convertRelativeLinksToAbsolute(markdown, baseUrl, logger) {
|
|
|
400
400
|
* @param {string} indent - Current indentation level (increases with nesting)
|
|
401
401
|
* @returns {Object} Count object: { found: number, notFound: number }
|
|
402
402
|
*/
|
|
403
|
-
function collectNavItems(items, collector, siteUrl, pagesByPath,
|
|
403
|
+
function collectNavItems(items, collector, siteUrl, pagesByPath, indent = '') {
|
|
404
404
|
let foundCount = 0;
|
|
405
405
|
let notFoundCount = 0;
|
|
406
406
|
|
|
@@ -439,7 +439,6 @@ function collectNavItems(items, collector, siteUrl, pagesByPath, logger, indent
|
|
|
439
439
|
} else {
|
|
440
440
|
// URL in navigation doesn't match any page in pagesByPath
|
|
441
441
|
notFoundCount++;
|
|
442
|
-
logger.debug(`Navigation item URL not found in pages: ${urlPath}`);
|
|
443
442
|
}
|
|
444
443
|
}
|
|
445
444
|
|
|
@@ -456,7 +455,6 @@ function collectNavItems(items, collector, siteUrl, pagesByPath, logger, indent
|
|
|
456
455
|
collector,
|
|
457
456
|
siteUrl,
|
|
458
457
|
pagesByPath,
|
|
459
|
-
logger,
|
|
460
458
|
indent + ' ' // Add 2 spaces for each nesting level
|
|
461
459
|
);
|
|
462
460
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pointsharp/antora-llm-generator",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.1",
|
|
5
5
|
"description": "An Antora extension to generate llms.txt files for LLM consumption following llmstxt.org specification.",
|
|
6
6
|
"main": "llm-generator.js",
|
|
7
7
|
"keywords": [
|