@voicenter-team/nuxt-llms-generator 0.1.7 → 0.1.9

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.
@@ -1,77 +1,42 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
2
2
  import { join, dirname, basename } from 'path';
3
+ import { transliterate } from 'transliteration';
3
4
  import Mustache from 'mustache';
4
5
  import Anthropic from '@anthropic-ai/sdk';
5
6
  import { createHash } from 'crypto';
6
7
  import { JSONPath } from 'jsonpath-plus';
7
- import { T as TemplateError, E as ErrorCode, w as withErrorHandling } from '../shared/nuxt-llms-generator.11eb2a36.mjs';
8
+ import { w as withErrorHandling } from '../shared/nuxt-llms-generator.bc139143.mjs';
8
9
  import '@nuxt/kit';
9
10
  import 'zod';
10
11
  import 'node-html-markdown';
11
12
 
12
- class AnthropicClient {
13
- client;
14
- model;
15
- maxRetries = 3;
16
- retryDelayMs = 1e3;
17
- constructor(config) {
18
- this.client = new Anthropic({
19
- apiKey: config.anthropicApiKey
20
- });
21
- this.model = config.anthropicModel || "claude-3-5-sonnet-20241022";
22
- }
23
- async generateTemplate(request) {
24
- const prompt = this.buildPrompt(request);
25
- for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
26
- try {
27
- const response = await this.client.messages.create({
28
- model: this.model,
29
- max_tokens: 4e3,
30
- temperature: 0.1,
31
- messages: [{
32
- role: "user",
33
- content: prompt
34
- }]
35
- });
36
- const content = response.content[0];
37
- if (content.type !== "text") {
38
- throw new Error("Unexpected response type from Anthropic API");
39
- }
40
- return this.parseResponse(content.text);
41
- } catch (error) {
42
- if (attempt === this.maxRetries) {
43
- throw new Error(`Anthropic API failed after ${this.maxRetries} attempts: ${error}`);
44
- }
45
- console.warn(`Anthropic API attempt ${attempt} failed, retrying...`, error);
46
- await this.delay(this.retryDelayMs * attempt);
47
- }
48
- }
49
- throw new Error("Failed to generate template");
50
- }
51
- buildPrompt(request) {
52
- return `# llms\u2011aware Mustache Template Generator Prompt (Key\u2011Agnostic)
13
+ function buildLLMSTemplatePrompt(request) {
14
+ return `# LLMS.txt-Optimized Mustache Template Generator
53
15
 
54
- You are an expert in crafting **Mustache.js templates** whose output is **clean Markdown pages** (with \`.md\` extension) intended for **LLM-friendly ingestion** following the [\`llms.txt\`](https://llmstxt.org/) standard.
16
+ You are an expert at creating **Mustache.js templates** that generate **LLM knowledge base entries** following the [\`llms.txt\` standard](https://llmstxt.org/).
55
17
 
56
18
  ---
57
19
 
58
- ## \u{1F3AF} Purpose & Goal
20
+ ## \u{1F3AF} TRUE PURPOSE: Help LLMs Answer Questions Efficiently
59
21
 
60
- The \`.md\` files generated from these templates represent the **structured, machine-readable versions of website pages**.
61
- They are designed to be indexed and understood by **Large Language Models** through the \`llms.txt\` manifest.
62
- Each \`.md\` file provides semantic, human-readable, and hierarchically organized content for LLMs to learn from.
22
+ **Critical Understanding:**
23
+ These \`.md\` files are **NOT website copies** \u2014 they are **LLM knowledge base entries** designed for **inference** (understanding), not training.
63
24
 
64
- Your task: Given an arbitrary JSON page structure (potentially deeply nested, dynamic, and unpredictable), produce a **generic Mustache template** that renders this JSON into an \`.md\` file conforming to \`llms.txt\` principles \u2014 emphasizing clarity, structure, and interpretability.
25
+ **Primary Goal:** Enable LLMs to quickly answer user questions about this website page within **limited context windows** (typically 200K tokens).
65
26
 
66
- These templates must be domain-agnostic: usable for **B2B SaaS**, **marketplaces**, **landing pages**, **blogs**, **docs**, or **personal websites**.
27
+ **What This Means:**
28
+ - An LLM will read this file to understand "What is this page about?"
29
+ - Users will ask questions like "What does this page offer?", "Who is this for?", "What are the key features?"
30
+ - Content must be **concise**, **scannable**, and **fact-dense**
31
+ - Prioritize information that helps LLMs **infer meaning** over complete content reproduction
67
32
 
68
33
  ---
69
34
 
70
- ## \u{1F4E5} Context Supplied to You
35
+ ## \u{1F4E5} Context Supplied
71
36
 
72
- - URL: ${request.url}
73
- - Template Alias: ${request.templateAlias}
74
- - JSON Path: ${request.jpath}
37
+ - **URL:** ${request.url}
38
+ - **Template Alias:** ${request.templateAlias}
39
+ - **JSON Path:** ${request.jpath}
75
40
 
76
41
  ### Available Data
77
42
  \`\`\`json
@@ -80,102 +45,200 @@ ${JSON.stringify(request.pageContent, null, 2)}
80
45
 
81
46
  ---
82
47
 
83
- ## \u{1F9E9} Core Principles (Key\u2011Agnostic)
48
+ ## \u{1F9E0} Content Philosophy: Think "Knowledge Base Entry"
84
49
 
85
- 1. **Do not assume fixed property names.**
86
- Infer content type and importance dynamically from value structure, length, and position.
50
+ ### 1. Start with Expert-Level Summary
51
+ - **First impression matters:** What would an expert say about this page in 1-2 sentences?
52
+ - Lead with **value proposition** or **core purpose**
53
+ - Use the blockquote format (\`> \`) for the summary \u2014 this signals importance
87
54
 
88
- 2. **Purpose\u2011Driven Hierarchy (LLMS.txt\u2011Friendly):**
89
- - Start with the main concept (\`#\` heading)
90
- - Follow with a one-paragraph value statement or summary (\`>\` blockquote)
91
- - Then expand into structured content with \`##\`, \`###\`, and lists
55
+ ### 2. Structure for Question-Answering
56
+ Anticipate questions an LLM might need to answer:
57
+ - "What is this?" \u2192 Main heading + summary
58
+ - "What does it do/offer?" \u2192 Key features/benefits section
59
+ - "Who is it for?" \u2192 Target audience/use cases
60
+ - "How does it work?" \u2192 Process/methodology
61
+ - "What are the details?" \u2192 Technical specs/pricing/etc.
92
62
 
93
- 3. **Semantic Markdown Only**
94
- Use only Markdown syntax (\`#\`, \`>\`, \`-\`, \`[link](url)\`, \`![alt](img)\`).
95
- **No HTML**, **no entities**, **no inline styles**, **no attributes**.
63
+ ### 3. Prioritize Information by Importance
64
+ **Essential First:**
65
+ - What this page represents
66
+ - Primary value/purpose
67
+ - Key differentiators
96
68
 
97
- 4. **Dynamic Interpretation**
98
- - Short textual fields near the root likely represent titles.
99
- - Long text blocks indicate overviews, descriptions, or stories.
100
- - Arrays of primitives \u2192 bullet lists.
101
- - Arrays of objects \u2192 repeated sub-sections or tables.
102
- - Objects \u2192 nested sections with humanized headings.
69
+ **Supporting Details Second:**
70
+ - Features, benefits, specifications
71
+ - Use cases, examples
72
+ - Technical details
103
73
 
104
- 5. **Exact Property Bindings**
105
- When referencing values, always use the **exact property name** from JSON (\`{{keyName}}\`).
106
- Do not rename or modify binding identifiers.
74
+ **Peripheral Information Last:**
75
+ - Meta information, related links
76
+ - Supplementary context
107
77
 
108
- 6. **Humanized Section Titles**
109
- Convert keys into readable Markdown headings:
110
- e.g., \`productDetails\` \u2192 \u201CProduct Details\u201D, \`seo_meta\` \u2192 \u201CSEO Meta\u201D.
111
- These headings are for readability; the Mustache bindings remain exact.
78
+ ### 4. Optimize for Scanability
79
+ - Use **hierarchical headings** (\`#\`, \`##\`, \`###\`) to create clear structure
80
+ - Employ **bullet lists** for scannable facts
81
+ - Keep paragraphs **short and dense** (2-3 sentences max)
82
+ - Use **semantic Markdown** only \u2014 no HTML, entities, or attributes
112
83
 
113
- 7. **Omit Noise**
114
- Exclude non-content fields like IDs, timestamps, internal flags, etc.
84
+ ---
115
85
 
116
- 8. **URL Handling**
117
- - If a value looks like a URL, render \`[Label]({{key}})\`.
118
- - If image URL, render \`![Alt text]({{key}})\`.
86
+ ## \u{1F527} Technical Principles (Key-Agnostic Design)
119
87
 
120
- 9. **Recursion & Nesting**
121
- Apply the same logic recursively to all nested objects and arrays.
122
- Heading depth corresponds to the nesting level, but avoid exceeding four \`#\` levels.
88
+ ### 1. Dynamic Property Inference
89
+ **Do not assume fixed property names.** Infer content type and importance from:
90
+ - **Value structure:** Object, array, string, number
91
+ - **Value length:** Short strings = titles; long text = descriptions
92
+ - **Position in JSON:** Root-level = high importance; nested = contextual details
93
+ - **Semantic patterns:** URLs, images, dates, IDs
123
94
 
124
- ---
95
+ ### 2. Exact Property Bindings
96
+ - Always use the **exact property name** from JSON: \`{{actualKeyName}}\`
97
+ - Do NOT rename or modify binding identifiers
98
+ - The Mustache bindings must match JSON precisely
99
+
100
+ ### 3. Humanized Section Headings
101
+ While bindings stay exact, convert keys to readable headings:
102
+ - \`productFeatures\` \u2192 "Product Features"
103
+ - \`pricing_tiers\` \u2192 "Pricing Tiers"
104
+ - \`techSpecs\` \u2192 "Technical Specifications"
125
105
 
126
- ## \u{1F9E0} LLMS.txt Relevance
106
+ ### 4. Semantic Interpretation Guide
107
+ - **Short root strings (5-50 chars)** \u2192 Likely page title
108
+ - **Medium text (50-300 chars)** \u2192 Likely summary/tagline
109
+ - **Long text (300+ chars)** \u2192 Likely detailed description
110
+ - **Arrays of primitives** \u2192 Bullet lists
111
+ - **Arrays of objects** \u2192 Repeated sections or tables
112
+ - **Nested objects** \u2192 Sub-sections with logical hierarchy
113
+ - **URL-like strings** \u2192 Render as \`[Label]({{url}})\`
114
+ - **Image URLs** \u2192 Render as \`![Description]({{imageUrl}})\`
127
115
 
128
- The resulting \`.md\` output should be suitable for inclusion in or referencing from \`llms.txt\`.
129
- Each file acts as an **LLM-ingestible mirror** of the website content \u2014 optimized for understanding by AI systems.
130
- Focus on **semantic clarity**, **hierarchical consistency**, and **contextual richness**.
131
- This ensures that LLMs reading the \`.md\` files will correctly infer what each page is about.
116
+ ### 5. Noise Filtering
117
+ **Exclude non-content fields:**
118
+ - IDs (\`id\`, \`nodeId\`, \`_id\`)
119
+ - Timestamps (\`createdAt\`, \`updatedAt\`, \`lastModified\`)
120
+ - Internal flags (\`isPublished\`, \`sortOrder\`, \`hidden\`)
121
+ - System metadata (\`_type\`, \`contentType\`, \`template\`)
122
+
123
+ ### 6. Hierarchy & Nesting
124
+ - **Root level** \u2192 \`#\` (H1) \u2014 one per document
125
+ - **Primary sections** \u2192 \`##\` (H2)
126
+ - **Sub-sections** \u2192 \`###\` (H3)
127
+ - **Details** \u2192 \`####\` (H4) \u2014 avoid going deeper
128
+ - Heading depth corresponds to JSON nesting, but stay practical
132
129
 
133
130
  ---
134
131
 
135
- ## \u{1F9F1} Example Structural Patterns
132
+ ## \u{1F4D0} Template Structure Pattern
133
+
134
+ ### Mandatory Opening
135
+ \`\`\`mustache
136
+ # {{primaryTitle}}
137
+
138
+ {{#summaryOrTagline}}
139
+ > {{summaryOrTagline}}
140
+ {{/summaryOrTagline}}
141
+ \`\`\`
136
142
 
137
- ### Generic Page Template
143
+ ### Recommended Sections (adapt to JSON)
138
144
  \`\`\`mustache
139
- # {{mainTitle}}
145
+ {{#mainDescription}}
146
+ {{mainDescription}}
147
+ {{/mainDescription}}
140
148
 
141
- {{#summary}}
142
- > {{summary}}
143
- {{/summary}}
149
+ {{#keyFeatures.0}}
150
+ ## Key Features
151
+ {{#keyFeatures}}
152
+ - **{{featureName}}**: {{featureDescription}}
153
+ {{/keyFeatures}}
154
+ {{/keyFeatures.0}}
144
155
 
145
- {{#sections.0}}
146
- ## {{sectionTitle}}
147
- {{#sections}}
148
- ### {{itemTitle}}
149
- {{itemDescription}}
150
- {{/sections}}
151
- {{/sections.0}}
156
+ {{#useCases.0}}
157
+ ## Use Cases
158
+ {{#useCases}}
159
+ ### {{caseTitle}}
160
+ {{caseDescription}}
161
+ {{/useCases}}
162
+ {{/useCases.0}}
152
163
 
153
- {{#links.0}}
154
- ## Links
155
- {{#links}}
156
- - [{{label}}]({{url}})
157
- {{/links}}
158
- {{/links.0}}
164
+ {{#technicalDetails.0}}
165
+ ## Technical Details
166
+ {{#technicalDetails}}
167
+ - **{{detailLabel}}**: {{detailValue}}
168
+ {{/technicalDetails}}
169
+ {{/technicalDetails.0}}
159
170
  \`\`\`
160
171
 
161
- > Example only \u2014 the actual template must reflect the JSON shape dynamically.
172
+ **Note:** This is an illustrative pattern. Adapt section names and structure to match the actual JSON dynamically.
162
173
 
163
174
  ---
164
175
 
165
176
  ## \u2705 Output Requirements
166
177
 
167
- - Output **only** the Mustache template (no extra text, no code fences).
168
- - Use **exact JSON property names** in bindings.
169
- - Render **clean, human-readable Markdown** suitable for \`llms.txt\`.
170
- - Maintain logical hierarchy derived from JSON structure.
171
- - Avoid domain-specific or brand-specific logic.
178
+ 1. **Output ONLY the Mustache template** \u2014 no explanations, no code fences, no preamble
179
+ 2. **Use exact JSON property names** in all bindings
180
+ 3. **Generate clean Markdown** \u2014 no HTML, entities, or attributes
181
+ 4. **Prioritize content** \u2014 most important information first
182
+ 5. **Be concise** \u2014 optimize for limited context windows
183
+ 6. **Structure for questions** \u2014 LLMs should easily extract facts
184
+ 7. **Stay domain-agnostic** \u2014 template should work for any JSON shape
172
185
 
173
186
  ---
174
187
 
175
- ## \u{1F680} Task
188
+ ## \u{1F680} Your Task
176
189
 
177
- Analyze the provided JSON and **generate the Mustache template** that will produce the \`.md\` page following these rules.
190
+ Analyze the provided JSON structure and **generate a Mustache template** that produces an **LLM knowledge base entry** following these principles.
191
+
192
+ **Think:**
193
+ - What would an LLM need to know to answer questions about this page?
194
+ - What's the core value/purpose this page communicates?
195
+ - How can I structure this for maximum inference efficiency?
196
+
197
+ Generate the template now.
178
198
  `;
199
+ }
200
+
201
+ class AnthropicClient {
202
+ client;
203
+ model;
204
+ maxRetries = 3;
205
+ retryDelayMs = 1e3;
206
+ constructor(config) {
207
+ this.client = new Anthropic({
208
+ apiKey: config.anthropicApiKey
209
+ });
210
+ this.model = config.anthropicModel || "claude-3-5-sonnet-20241022";
211
+ }
212
+ async generateTemplate(request) {
213
+ const prompt = this.buildPrompt(request);
214
+ for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
215
+ try {
216
+ const response = await this.client.messages.create({
217
+ model: this.model,
218
+ max_tokens: 4e3,
219
+ temperature: 0.1,
220
+ messages: [{
221
+ role: "user",
222
+ content: prompt
223
+ }]
224
+ });
225
+ const content = response.content[0];
226
+ if (content.type !== "text") {
227
+ throw new Error("Unexpected response type from Anthropic API");
228
+ }
229
+ return this.parseResponse(content.text);
230
+ } catch (error) {
231
+ if (attempt === this.maxRetries) {
232
+ throw new Error(`Anthropic API failed after ${this.maxRetries} attempts: ${error}`);
233
+ }
234
+ console.warn(`Anthropic API attempt ${attempt} failed, retrying...`, error);
235
+ await this.delay(this.retryDelayMs * attempt);
236
+ }
237
+ }
238
+ throw new Error("Failed to generate template");
239
+ }
240
+ buildPrompt(request) {
241
+ return buildLLMSTemplatePrompt(request);
179
242
  }
180
243
  parseResponse(responseText) {
181
244
  const codeBlockRegex = /```(?:mustache)?\n?([\s\S]*?)```/;
@@ -896,245 +959,6 @@ async function performAutomaticCleanup(umbracoData, cacheDir, options = {}) {
896
959
  return stats;
897
960
  }
898
961
 
899
- class MustacheSyntaxValidator {
900
- name = "mustache-syntax";
901
- canFix = true;
902
- validate(template) {
903
- const result = {
904
- isValid: true,
905
- errors: [],
906
- warnings: []
907
- };
908
- try {
909
- Mustache.parse(template);
910
- return result;
911
- } catch (error) {
912
- result.isValid = false;
913
- result.errors.push(`Mustache syntax error: ${error.message}`);
914
- if (this.canFix) {
915
- try {
916
- const fixedTemplate = this.fix(template);
917
- result.fixedTemplate = fixedTemplate;
918
- result.warnings.push("Template was automatically fixed");
919
- } catch (fixError) {
920
- result.errors.push(`Could not fix template: ${fixError.message}`);
921
- }
922
- }
923
- return result;
924
- }
925
- }
926
- fix(template) {
927
- let fixedTemplate = template;
928
- const openSectionRegex = /\{\{\#([a-zA-Z0-9_.]+)\}\}/g;
929
- const closeSectionRegex = /\{\{\/([a-zA-Z0-9_.]+)\}\}/g;
930
- const openSections = [];
931
- const closeSections = [];
932
- let match;
933
- while ((match = openSectionRegex.exec(template)) !== null) {
934
- openSections.push({
935
- name: match[1],
936
- pos: match.index
937
- });
938
- }
939
- while ((match = closeSectionRegex.exec(template)) !== null) {
940
- closeSections.push({
941
- name: match[1],
942
- pos: match.index
943
- });
944
- }
945
- const unmatchedOpens = openSections.filter(
946
- (open) => !closeSections.some((close) => close.name === open.name)
947
- );
948
- const unmatchedCloses = closeSections.filter(
949
- (close) => !openSections.some((open) => open.name === close.name)
950
- );
951
- unmatchedOpens.forEach((unmatched) => {
952
- const sectionRegex = new RegExp(`\\{\\{#${unmatched.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\}\\}`, "g");
953
- fixedTemplate = fixedTemplate.replace(sectionRegex, "");
954
- });
955
- unmatchedCloses.forEach((unmatched) => {
956
- const sectionRegex = new RegExp(`\\{\\{/${unmatched.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\}\\}`, "g");
957
- fixedTemplate = fixedTemplate.replace(sectionRegex, "");
958
- });
959
- fixedTemplate = fixedTemplate.replace(/\n\s*\n\s*\n/g, "\n\n");
960
- return fixedTemplate;
961
- }
962
- }
963
- class TemplateStructureValidator {
964
- name = "template-structure";
965
- validate(template) {
966
- const result = {
967
- isValid: true,
968
- errors: [],
969
- warnings: []
970
- };
971
- if (!template.match(/^#\s+/m)) {
972
- result.warnings.push("Template should start with a heading (# Title)");
973
- }
974
- const headingLevels = (template.match(/^#{4,}/gm) || []).length;
975
- if (headingLevels > 0) {
976
- result.warnings.push("Template has deeply nested headings (4+ levels), consider flattening structure");
977
- }
978
- const emptySections = template.match(/\{\{#\w+\}\}\s*\{\{\/\w+\}\}/g);
979
- if (emptySections) {
980
- result.warnings.push(`Found ${emptySections.length} empty sections that may not render content`);
981
- }
982
- const commonTypos = template.match(/\{\{\s*pageTittle\s*\}\}/g);
983
- if (commonTypos) {
984
- result.warnings.push('Found "pageTittle" - check if this should be "pageTitle"');
985
- }
986
- return result;
987
- }
988
- }
989
- class ContentCompletenessValidator {
990
- name = "content-completeness";
991
- validate(template) {
992
- const result = {
993
- isValid: true,
994
- errors: [],
995
- warnings: []
996
- };
997
- const variables = this.extractVariables(template);
998
- const hasTitle = variables.some((v) => v.includes("title") || v.includes("Title"));
999
- if (!hasTitle) {
1000
- result.warnings.push("Template missing title variable (pageTitle, title, etc.)");
1001
- }
1002
- const hasDescription = variables.some((v) => v.includes("description") || v.includes("Description"));
1003
- if (!hasDescription) {
1004
- result.warnings.push("Template missing description variable");
1005
- }
1006
- if (template.length < 50) {
1007
- result.warnings.push("Template is very short, may not provide sufficient content");
1008
- }
1009
- const sectionsOnly = variables.filter((v) => !v.includes(".") && !v.includes("["));
1010
- if (sectionsOnly.length < 2) {
1011
- result.warnings.push("Template has limited content variables, consider adding more sections");
1012
- }
1013
- return result;
1014
- }
1015
- extractVariables(template) {
1016
- const variableRegex = /\{\{\s*([^#\/][^}]*?)\s*\}\}/g;
1017
- const variables = [];
1018
- let match;
1019
- while ((match = variableRegex.exec(template)) !== null) {
1020
- variables.push(match[1].trim());
1021
- }
1022
- return variables;
1023
- }
1024
- }
1025
- class LLMSTxtComplianceValidator {
1026
- name = "llms-txt-compliance";
1027
- validate(template) {
1028
- const result = {
1029
- isValid: true,
1030
- errors: [],
1031
- warnings: []
1032
- };
1033
- const headings = template.match(/^#+\s+.+$/gm) || [];
1034
- let lastLevel = 0;
1035
- let hasProperHierarchy = true;
1036
- headings.forEach((heading) => {
1037
- const level = (heading.match(/^#+/) || [""])[0].length;
1038
- if (level > lastLevel + 1) {
1039
- hasProperHierarchy = false;
1040
- }
1041
- lastLevel = level;
1042
- });
1043
- if (!hasProperHierarchy) {
1044
- result.warnings.push("Heading hierarchy should increment by one level (# -> ## -> ###)");
1045
- }
1046
- if (template.includes("pageDescription") && !template.includes(">")) {
1047
- result.warnings.push("Consider using blockquote (>) for page description as per LLMS.txt standard");
1048
- }
1049
- const hasLists = template.includes("- ") || template.includes("* ");
1050
- if (!hasLists && template.length > 200) {
1051
- result.warnings.push("Long content without lists - consider breaking into bullet points for better AI consumption");
1052
- }
1053
- const htmlTags = (template.match(/<[^>]+>/g) || []).length;
1054
- if (htmlTags > 3) {
1055
- result.warnings.push("Template contains HTML tags - prefer pure markdown for LLMS.txt compliance");
1056
- }
1057
- return result;
1058
- }
1059
- }
1060
- class TemplateValidationPipeline {
1061
- validators = [];
1062
- constructor() {
1063
- this.addValidator(new MustacheSyntaxValidator());
1064
- this.addValidator(new TemplateStructureValidator());
1065
- this.addValidator(new ContentCompletenessValidator());
1066
- this.addValidator(new LLMSTxtComplianceValidator());
1067
- }
1068
- addValidator(validator) {
1069
- this.validators.push(validator);
1070
- }
1071
- removeValidator(name) {
1072
- this.validators = this.validators.filter((v) => v.name !== name);
1073
- }
1074
- async validateTemplate(template, options = {}) {
1075
- const { autoFix = true, throwOnError = false } = options;
1076
- let currentTemplate = template;
1077
- const allResults = {
1078
- isValid: true,
1079
- errors: [],
1080
- warnings: []
1081
- };
1082
- for (const validator of this.validators) {
1083
- const result = validator.validate(currentTemplate);
1084
- allResults.errors.push(...result.errors);
1085
- allResults.warnings.push(...result.warnings);
1086
- if (!result.isValid) {
1087
- allResults.isValid = false;
1088
- if (autoFix && validator.canFix && validator.fix) {
1089
- const fixedTemplate = validator.fix(currentTemplate);
1090
- const fixResult = validator.validate(fixedTemplate);
1091
- if (fixResult.isValid) {
1092
- currentTemplate = fixedTemplate;
1093
- allResults.fixedTemplate = currentTemplate;
1094
- console.log(`Template fixed by ${validator.name} validator`);
1095
- allResults.errors = allResults.errors.filter((e) => !result.errors.includes(e));
1096
- if (allResults.errors.length === 0) {
1097
- allResults.isValid = true;
1098
- }
1099
- }
1100
- }
1101
- }
1102
- }
1103
- if (allResults.errors.length > 0 && throwOnError) {
1104
- throw new TemplateError(
1105
- ErrorCode.TEMPLATE_VALIDATION_FAILED,
1106
- `Template validation failed: ${allResults.errors.join(", ")}`,
1107
- { template: template.substring(0, 200) + "..." }
1108
- );
1109
- }
1110
- return allResults;
1111
- }
1112
- async validateAndFix(template) {
1113
- const result = await this.validateTemplate(template, {
1114
- autoFix: true,
1115
- throwOnError: false
1116
- });
1117
- if (result.fixedTemplate) {
1118
- return result.fixedTemplate;
1119
- }
1120
- if (result.errors.length > 0) {
1121
- console.warn("Could not fix template, using fallback");
1122
- return `# {{pageTitle}}
1123
-
1124
- > {{pageDescription}}
1125
-
1126
- ## Content
1127
-
1128
- This page content could not be processed due to template formatting issues.`;
1129
- }
1130
- return template;
1131
- }
1132
- getValidatorNames() {
1133
- return this.validators.map((v) => v.name);
1134
- }
1135
- }
1136
- const templateValidationPipeline = new TemplateValidationPipeline();
1137
-
1138
962
  class TemplateGenerator {
1139
963
  anthropicClient;
1140
964
  promptAnalyzer;
@@ -1279,8 +1103,7 @@ class TemplateGenerator {
1279
1103
  }
1280
1104
  async renderTemplate(template, data) {
1281
1105
  return withErrorHandling(async () => {
1282
- const validatedTemplate = await templateValidationPipeline.validateAndFix(template);
1283
- return Mustache.render(validatedTemplate, data);
1106
+ return Mustache.render(template, data);
1284
1107
  }, {
1285
1108
  template: template.substring(0, 200) + "...",
1286
1109
  dataKeys: Object.keys(data)
@@ -1579,7 +1402,7 @@ class LLMSFilesGenerator {
1579
1402
  if (filename.startsWith("-") || filename.startsWith(".")) {
1580
1403
  filename = "page-" + filename.replace(/^[-.]/, "");
1581
1404
  }
1582
- return filename;
1405
+ return transliterate(filename);
1583
1406
  }
1584
1407
  getLLMSFilePath(fullPath) {
1585
1408
  const filename = basename(fullPath);
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.1.7"
7
+ "version": "0.1.9"
8
8
  }
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { l as default } from './shared/nuxt-llms-generator.11eb2a36.mjs';
1
+ export { l as default } from './shared/nuxt-llms-generator.bc139143.mjs';
2
2
  import '@nuxt/kit';
3
3
  import 'fs';
4
4
  import 'path';
@@ -58,28 +58,6 @@ class SchemaValidator {
58
58
  }
59
59
  }
60
60
 
61
- var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
62
- ErrorCode2["INVALID_CONFIG"] = "INVALID_CONFIG";
63
- ErrorCode2["MISSING_API_KEY"] = "MISSING_API_KEY";
64
- ErrorCode2["INVALID_DATA_PATH"] = "INVALID_DATA_PATH";
65
- ErrorCode2["INVALID_UMBRACO_DATA"] = "INVALID_UMBRACO_DATA";
66
- ErrorCode2["PAGE_CONTENT_EXTRACTION_FAILED"] = "PAGE_CONTENT_EXTRACTION_FAILED";
67
- ErrorCode2["INVALID_JPATH"] = "INVALID_JPATH";
68
- ErrorCode2["ANTHROPIC_API_ERROR"] = "ANTHROPIC_API_ERROR";
69
- ErrorCode2["ANTHROPIC_CONNECTION_FAILED"] = "ANTHROPIC_CONNECTION_FAILED";
70
- ErrorCode2["TEMPLATE_GENERATION_FAILED"] = "TEMPLATE_GENERATION_FAILED";
71
- ErrorCode2["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED";
72
- ErrorCode2["TEMPLATE_VALIDATION_FAILED"] = "TEMPLATE_VALIDATION_FAILED";
73
- ErrorCode2["TEMPLATE_RENDERING_FAILED"] = "TEMPLATE_RENDERING_FAILED";
74
- ErrorCode2["MUSTACHE_SYNTAX_ERROR"] = "MUSTACHE_SYNTAX_ERROR";
75
- ErrorCode2["FILE_READ_ERROR"] = "FILE_READ_ERROR";
76
- ErrorCode2["FILE_WRITE_ERROR"] = "FILE_WRITE_ERROR";
77
- ErrorCode2["DIRECTORY_CREATION_FAILED"] = "DIRECTORY_CREATION_FAILED";
78
- ErrorCode2["CACHE_READ_ERROR"] = "CACHE_READ_ERROR";
79
- ErrorCode2["CACHE_WRITE_ERROR"] = "CACHE_WRITE_ERROR";
80
- ErrorCode2["CACHE_CORRUPTED"] = "CACHE_CORRUPTED";
81
- return ErrorCode2;
82
- })(ErrorCode || {});
83
61
  class LLMSError extends Error {
84
62
  code;
85
63
  context;
@@ -117,12 +95,6 @@ class AnthropicAPIError extends LLMSError {
117
95
  this.retryable = retryable;
118
96
  }
119
97
  }
120
- class TemplateError extends LLMSError {
121
- constructor(code, message, context, cause) {
122
- super(code, message, context, cause);
123
- this.name = "TemplateError";
124
- }
125
- }
126
98
  class FileSystemError extends LLMSError {
127
99
  path;
128
100
  constructor(code, message, path, context, cause) {
@@ -379,4 +351,4 @@ async function generateLLMSFiles(config, umbracoData, logger) {
379
351
  }
380
352
  }
381
353
 
382
- export { ErrorCode as E, TemplateError as T, llmsModule as l, withErrorHandling as w };
354
+ export { llmsModule as l, withErrorHandling as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voicenter-team/nuxt-llms-generator",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Nuxt 3 module for automatically generating AI-optimized documentation files (llms.txt, llms-full.txt, and individual .md files) from Umbraco CMS data using Anthropic's Claude API.",
5
5
  "repository": "https://github.com/VoicenterTeam/nuxt-llms-generator",
6
6
  "license": "MIT",
@@ -41,6 +41,7 @@
41
41
  "mustache": "^4.2.0",
42
42
  "node-html-markdown": "^1.3.0",
43
43
  "npm": "^11.6.0",
44
+ "transliteration": "^2.3.5",
44
45
  "zod": "^4.1.9"
45
46
  },
46
47
  "devDependencies": {