@lacspace/llms-txt 1.2.0 → 1.2.2
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 +10 -0
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -126,4 +126,14 @@ This package is **free** under the **[Lacspace Free Licence](https://lacspace.co
|
|
|
126
126
|
|
|
127
127
|
Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
|
|
128
128
|
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
<div align="center">
|
|
132
|
+
|
|
133
|
+
**Part of the Lacspace ecosystem — 35 zero-dependency, isomorphic TypeScript packages.**
|
|
134
|
+
|
|
135
|
+
[All packages ↗](https://lacspace.com/packages) · [npm org ↗](https://www.npmjs.com/org/lacspace) · [Licence Centre ↗](https://lacspace.com/licenses) · [GitHub ↗](https://github.com/lacspace/npm-packages)
|
|
136
|
+
|
|
137
|
+
</div>
|
|
138
|
+
|
|
129
139
|
<div align="center"><sub>Built with care by <a href="https://lacspace.com">Lacspace</a> · Lacspace Free Licence · <a href="https://github.com/lacspace/npm-packages">source</a></sub></div>
|
package/dist/index.cjs
CHANGED
|
@@ -58,7 +58,13 @@ function titleFromUrl(url) {
|
|
|
58
58
|
}
|
|
59
59
|
const seg = path.split("/").filter(Boolean).pop();
|
|
60
60
|
if (!seg) return "Home";
|
|
61
|
-
|
|
61
|
+
let decoded;
|
|
62
|
+
try {
|
|
63
|
+
decoded = decodeURIComponent(seg);
|
|
64
|
+
} catch {
|
|
65
|
+
decoded = seg;
|
|
66
|
+
}
|
|
67
|
+
return decoded.replace(/\.[a-z]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
62
68
|
}
|
|
63
69
|
function llmsTxtFromSitemap(entries, meta) {
|
|
64
70
|
const bySection = /* @__PURE__ */ new Map();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/llms-txt\n * Generate and parse llms.txt and llms-full.txt (the llmstxt.org standard).\n *\n * llms.txt is a Markdown file at your site root that gives LLMs a curated map of\n * your most useful content; llms-full.txt inlines the full text so a model can\n * read everything in one request.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface LlmsLink {\n title: string;\n url: string;\n /** Short note shown after the link. */\n notes?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\nexport interface LlmsDoc {\n /** The site / project name (rendered as the H1). */\n title: string;\n /** One-line summary (rendered as a blockquote). */\n summary?: string;\n /** Free-form Markdown shown before the sections. */\n details?: string;\n sections: LlmsSection[];\n}\n\n/**\n * Render an `llms.txt` document.\n * @example\n * llmsTxt({\n * title: \"Lacspace\",\n * summary: \"Open-source TypeScript packages and products.\",\n * sections: [{ title: \"Docs\", links: [{ title: \"Packages\", url: \"https://lacspace.com/packages\" }] }],\n * });\n */\nexport function llmsTxt(doc: LlmsDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n if (doc.details) out.push(\"\", doc.details.trim());\n for (const section of doc.sections) {\n out.push(\"\", `## ${section.title}`, \"\");\n for (const l of section.links) {\n out.push(`- [${l.title}](${l.url})${l.notes ? `: ${l.notes}` : \"\"}`);\n }\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\nexport interface LlmsFullSection {\n title: string;\n /** Full Markdown content for this section. */\n content: string;\n /** Optional source URL, added as a heading link. */\n url?: string;\n}\n\nexport interface LlmsFullDoc {\n title: string;\n summary?: string;\n sections: LlmsFullSection[];\n}\n\n/** Render an `llms-full.txt` document with the full content inlined. */\nexport function llmsFullTxt(doc: LlmsFullDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n for (const section of doc.sections) {\n out.push(\"\", \"---\", \"\", `## ${section.title}`);\n if (section.url) out.push(\"\", `Source: ${section.url}`);\n out.push(\"\", section.content.trim());\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\n/** Parse an `llms.txt` string back into a structured document. */\nexport function parseLlmsTxt(txt: string): LlmsDoc {\n const lines = txt.split(/\\r?\\n/);\n const doc: LlmsDoc = { title: \"\", sections: [] };\n let current: LlmsSection | null = null;\n const detailBuf: string[] = [];\n const linkRe = /^-\\s*\\[([^\\]]+)\\]\\(([^)]+)\\)\\s*(?::\\s*(.*))?$/;\n\n for (const raw of lines) {\n const line = raw.trimEnd();\n if (line.startsWith(\"# \")) {\n doc.title = line.slice(2).trim();\n } else if (line.startsWith(\"> \")) {\n doc.summary = (doc.summary ? doc.summary + \" \" : \"\") + line.slice(2).trim();\n } else if (line.startsWith(\"## \")) {\n current = { title: line.slice(3).trim(), links: [] };\n doc.sections.push(current);\n } else if (current && linkRe.test(line)) {\n const m = line.match(linkRe)!;\n current.links.push({ title: m[1]!, url: m[2]!, notes: m[3]?.trim() || undefined });\n } else if (!current && line && !line.startsWith(\"#\")) {\n detailBuf.push(line);\n }\n }\n const details = detailBuf.join(\"\\n\").trim();\n if (details) doc.details = details;\n return doc;\n}\n\n/* ------------------------------ from sitemap ------------------------------ */\n\n/** A sitemap-ish entry — accepts `url` or `loc`, plus optional title/section. */\nexport interface SitemapEntryLike {\n url?: string;\n loc?: string;\n title?: string;\n section?: string;\n}\n\nfunction titleFromUrl(url: string): string {\n let path: string;\n try {\n path = new URL(url).pathname;\n } catch {\n path = url;\n }\n const seg = path.split(\"/\").filter(Boolean).pop();\n if (!seg) return \"Home\";\n return decodeURIComponent(seg).replace(/\\.[a-z]+$/i, \"\").replace(/[-_]+/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Build an `llms.txt` from a list of sitemap entries — group by `section`,\n * derive titles from the URL when not given. Pairs with `@lacspace/sitemap`.\n */\nexport function llmsTxtFromSitemap(\n entries: SitemapEntryLike[],\n meta: { title: string; summary?: string; details?: string; defaultSection?: string },\n): string {\n const bySection = new Map<string, LlmsLink[]>();\n for (const e of entries) {\n const url = e.url ?? e.loc;\n if (!url) continue;\n const section = e.section ?? meta.defaultSection ?? \"Pages\";\n if (!bySection.has(section)) bySection.set(section, []);\n bySection.get(section)!.push({ title: e.title ?? titleFromUrl(url), url });\n }\n const doc: LlmsDoc = {\n title: meta.title,\n summary: meta.summary,\n details: meta.details,\n sections: [...bySection].map(([title, links]) => ({ title, links })),\n };\n return llmsTxt(doc);\n}\n\n/* ------------------------------ adapters ------------------------------ */\n\n/** `llms.txt` as a Fetch/edge `Response` (text/plain) for app/llms.txt/route.ts. */\nexport function llmsTxtResponse(doc: LlmsDoc, init: ResponseInit = {}): Response {\n return new Response(llmsTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n\n/** `llms-full.txt` as a Fetch/edge `Response` (text/plain). */\nexport function llmsFullTxtResponse(doc: LlmsFullDoc, init: ResponseInit = {}): Response {\n return new Response(llmsFullTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/llms-txt\n * Generate and parse llms.txt and llms-full.txt (the llmstxt.org standard).\n *\n * llms.txt is a Markdown file at your site root that gives LLMs a curated map of\n * your most useful content; llms-full.txt inlines the full text so a model can\n * read everything in one request.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface LlmsLink {\n title: string;\n url: string;\n /** Short note shown after the link. */\n notes?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\nexport interface LlmsDoc {\n /** The site / project name (rendered as the H1). */\n title: string;\n /** One-line summary (rendered as a blockquote). */\n summary?: string;\n /** Free-form Markdown shown before the sections. */\n details?: string;\n sections: LlmsSection[];\n}\n\n/**\n * Render an `llms.txt` document.\n * @example\n * llmsTxt({\n * title: \"Lacspace\",\n * summary: \"Open-source TypeScript packages and products.\",\n * sections: [{ title: \"Docs\", links: [{ title: \"Packages\", url: \"https://lacspace.com/packages\" }] }],\n * });\n */\nexport function llmsTxt(doc: LlmsDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n if (doc.details) out.push(\"\", doc.details.trim());\n for (const section of doc.sections) {\n out.push(\"\", `## ${section.title}`, \"\");\n for (const l of section.links) {\n out.push(`- [${l.title}](${l.url})${l.notes ? `: ${l.notes}` : \"\"}`);\n }\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\nexport interface LlmsFullSection {\n title: string;\n /** Full Markdown content for this section. */\n content: string;\n /** Optional source URL, added as a heading link. */\n url?: string;\n}\n\nexport interface LlmsFullDoc {\n title: string;\n summary?: string;\n sections: LlmsFullSection[];\n}\n\n/** Render an `llms-full.txt` document with the full content inlined. */\nexport function llmsFullTxt(doc: LlmsFullDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n for (const section of doc.sections) {\n out.push(\"\", \"---\", \"\", `## ${section.title}`);\n if (section.url) out.push(\"\", `Source: ${section.url}`);\n out.push(\"\", section.content.trim());\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\n/** Parse an `llms.txt` string back into a structured document. */\nexport function parseLlmsTxt(txt: string): LlmsDoc {\n const lines = txt.split(/\\r?\\n/);\n const doc: LlmsDoc = { title: \"\", sections: [] };\n let current: LlmsSection | null = null;\n const detailBuf: string[] = [];\n const linkRe = /^-\\s*\\[([^\\]]+)\\]\\(([^)]+)\\)\\s*(?::\\s*(.*))?$/;\n\n for (const raw of lines) {\n const line = raw.trimEnd();\n if (line.startsWith(\"# \")) {\n doc.title = line.slice(2).trim();\n } else if (line.startsWith(\"> \")) {\n doc.summary = (doc.summary ? doc.summary + \" \" : \"\") + line.slice(2).trim();\n } else if (line.startsWith(\"## \")) {\n current = { title: line.slice(3).trim(), links: [] };\n doc.sections.push(current);\n } else if (current && linkRe.test(line)) {\n const m = line.match(linkRe)!;\n current.links.push({ title: m[1]!, url: m[2]!, notes: m[3]?.trim() || undefined });\n } else if (!current && line && !line.startsWith(\"#\")) {\n detailBuf.push(line);\n }\n }\n const details = detailBuf.join(\"\\n\").trim();\n if (details) doc.details = details;\n return doc;\n}\n\n/* ------------------------------ from sitemap ------------------------------ */\n\n/** A sitemap-ish entry — accepts `url` or `loc`, plus optional title/section. */\nexport interface SitemapEntryLike {\n url?: string;\n loc?: string;\n title?: string;\n section?: string;\n}\n\nfunction titleFromUrl(url: string): string {\n let path: string;\n try {\n path = new URL(url).pathname;\n } catch {\n path = url;\n }\n const seg = path.split(\"/\").filter(Boolean).pop();\n if (!seg) return \"Home\";\n let decoded: string;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n // Malformed percent-encoding — fall back to the raw segment instead of throwing.\n decoded = seg;\n }\n return decoded.replace(/\\.[a-z]+$/i, \"\").replace(/[-_]+/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Build an `llms.txt` from a list of sitemap entries — group by `section`,\n * derive titles from the URL when not given. Pairs with `@lacspace/sitemap`.\n */\nexport function llmsTxtFromSitemap(\n entries: SitemapEntryLike[],\n meta: { title: string; summary?: string; details?: string; defaultSection?: string },\n): string {\n const bySection = new Map<string, LlmsLink[]>();\n for (const e of entries) {\n const url = e.url ?? e.loc;\n if (!url) continue;\n const section = e.section ?? meta.defaultSection ?? \"Pages\";\n if (!bySection.has(section)) bySection.set(section, []);\n bySection.get(section)!.push({ title: e.title ?? titleFromUrl(url), url });\n }\n const doc: LlmsDoc = {\n title: meta.title,\n summary: meta.summary,\n details: meta.details,\n sections: [...bySection].map(([title, links]) => ({ title, links })),\n };\n return llmsTxt(doc);\n}\n\n/* ------------------------------ adapters ------------------------------ */\n\n/** `llms.txt` as a Fetch/edge `Response` (text/plain) for app/llms.txt/route.ts. */\nexport function llmsTxtResponse(doc: LlmsDoc, init: ResponseInit = {}): Response {\n return new Response(llmsTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n\n/** `llms-full.txt` as a Fetch/edge `Response` (text/plain). */\nexport function llmsFullTxtResponse(doc: LlmsFullDoc, init: ResponseInit = {}): Response {\n return new Response(llmsFullTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,13 @@ function titleFromUrl(url) {
|
|
|
56
56
|
}
|
|
57
57
|
const seg = path.split("/").filter(Boolean).pop();
|
|
58
58
|
if (!seg) return "Home";
|
|
59
|
-
|
|
59
|
+
let decoded;
|
|
60
|
+
try {
|
|
61
|
+
decoded = decodeURIComponent(seg);
|
|
62
|
+
} catch {
|
|
63
|
+
decoded = seg;
|
|
64
|
+
}
|
|
65
|
+
return decoded.replace(/\.[a-z]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
60
66
|
}
|
|
61
67
|
function llmsTxtFromSitemap(entries, meta) {
|
|
62
68
|
const bySection = /* @__PURE__ */ new Map();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/llms-txt\n * Generate and parse llms.txt and llms-full.txt (the llmstxt.org standard).\n *\n * llms.txt is a Markdown file at your site root that gives LLMs a curated map of\n * your most useful content; llms-full.txt inlines the full text so a model can\n * read everything in one request.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface LlmsLink {\n title: string;\n url: string;\n /** Short note shown after the link. */\n notes?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\nexport interface LlmsDoc {\n /** The site / project name (rendered as the H1). */\n title: string;\n /** One-line summary (rendered as a blockquote). */\n summary?: string;\n /** Free-form Markdown shown before the sections. */\n details?: string;\n sections: LlmsSection[];\n}\n\n/**\n * Render an `llms.txt` document.\n * @example\n * llmsTxt({\n * title: \"Lacspace\",\n * summary: \"Open-source TypeScript packages and products.\",\n * sections: [{ title: \"Docs\", links: [{ title: \"Packages\", url: \"https://lacspace.com/packages\" }] }],\n * });\n */\nexport function llmsTxt(doc: LlmsDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n if (doc.details) out.push(\"\", doc.details.trim());\n for (const section of doc.sections) {\n out.push(\"\", `## ${section.title}`, \"\");\n for (const l of section.links) {\n out.push(`- [${l.title}](${l.url})${l.notes ? `: ${l.notes}` : \"\"}`);\n }\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\nexport interface LlmsFullSection {\n title: string;\n /** Full Markdown content for this section. */\n content: string;\n /** Optional source URL, added as a heading link. */\n url?: string;\n}\n\nexport interface LlmsFullDoc {\n title: string;\n summary?: string;\n sections: LlmsFullSection[];\n}\n\n/** Render an `llms-full.txt` document with the full content inlined. */\nexport function llmsFullTxt(doc: LlmsFullDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n for (const section of doc.sections) {\n out.push(\"\", \"---\", \"\", `## ${section.title}`);\n if (section.url) out.push(\"\", `Source: ${section.url}`);\n out.push(\"\", section.content.trim());\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\n/** Parse an `llms.txt` string back into a structured document. */\nexport function parseLlmsTxt(txt: string): LlmsDoc {\n const lines = txt.split(/\\r?\\n/);\n const doc: LlmsDoc = { title: \"\", sections: [] };\n let current: LlmsSection | null = null;\n const detailBuf: string[] = [];\n const linkRe = /^-\\s*\\[([^\\]]+)\\]\\(([^)]+)\\)\\s*(?::\\s*(.*))?$/;\n\n for (const raw of lines) {\n const line = raw.trimEnd();\n if (line.startsWith(\"# \")) {\n doc.title = line.slice(2).trim();\n } else if (line.startsWith(\"> \")) {\n doc.summary = (doc.summary ? doc.summary + \" \" : \"\") + line.slice(2).trim();\n } else if (line.startsWith(\"## \")) {\n current = { title: line.slice(3).trim(), links: [] };\n doc.sections.push(current);\n } else if (current && linkRe.test(line)) {\n const m = line.match(linkRe)!;\n current.links.push({ title: m[1]!, url: m[2]!, notes: m[3]?.trim() || undefined });\n } else if (!current && line && !line.startsWith(\"#\")) {\n detailBuf.push(line);\n }\n }\n const details = detailBuf.join(\"\\n\").trim();\n if (details) doc.details = details;\n return doc;\n}\n\n/* ------------------------------ from sitemap ------------------------------ */\n\n/** A sitemap-ish entry — accepts `url` or `loc`, plus optional title/section. */\nexport interface SitemapEntryLike {\n url?: string;\n loc?: string;\n title?: string;\n section?: string;\n}\n\nfunction titleFromUrl(url: string): string {\n let path: string;\n try {\n path = new URL(url).pathname;\n } catch {\n path = url;\n }\n const seg = path.split(\"/\").filter(Boolean).pop();\n if (!seg) return \"Home\";\n return decodeURIComponent(seg).replace(/\\.[a-z]+$/i, \"\").replace(/[-_]+/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Build an `llms.txt` from a list of sitemap entries — group by `section`,\n * derive titles from the URL when not given. Pairs with `@lacspace/sitemap`.\n */\nexport function llmsTxtFromSitemap(\n entries: SitemapEntryLike[],\n meta: { title: string; summary?: string; details?: string; defaultSection?: string },\n): string {\n const bySection = new Map<string, LlmsLink[]>();\n for (const e of entries) {\n const url = e.url ?? e.loc;\n if (!url) continue;\n const section = e.section ?? meta.defaultSection ?? \"Pages\";\n if (!bySection.has(section)) bySection.set(section, []);\n bySection.get(section)!.push({ title: e.title ?? titleFromUrl(url), url });\n }\n const doc: LlmsDoc = {\n title: meta.title,\n summary: meta.summary,\n details: meta.details,\n sections: [...bySection].map(([title, links]) => ({ title, links })),\n };\n return llmsTxt(doc);\n}\n\n/* ------------------------------ adapters ------------------------------ */\n\n/** `llms.txt` as a Fetch/edge `Response` (text/plain) for app/llms.txt/route.ts. */\nexport function llmsTxtResponse(doc: LlmsDoc, init: ResponseInit = {}): Response {\n return new Response(llmsTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n\n/** `llms-full.txt` as a Fetch/edge `Response` (text/plain). */\nexport function llmsFullTxtResponse(doc: LlmsFullDoc, init: ResponseInit = {}): Response {\n return new Response(llmsFullTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/llms-txt\n * Generate and parse llms.txt and llms-full.txt (the llmstxt.org standard).\n *\n * llms.txt is a Markdown file at your site root that gives LLMs a curated map of\n * your most useful content; llms-full.txt inlines the full text so a model can\n * read everything in one request.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface LlmsLink {\n title: string;\n url: string;\n /** Short note shown after the link. */\n notes?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\nexport interface LlmsDoc {\n /** The site / project name (rendered as the H1). */\n title: string;\n /** One-line summary (rendered as a blockquote). */\n summary?: string;\n /** Free-form Markdown shown before the sections. */\n details?: string;\n sections: LlmsSection[];\n}\n\n/**\n * Render an `llms.txt` document.\n * @example\n * llmsTxt({\n * title: \"Lacspace\",\n * summary: \"Open-source TypeScript packages and products.\",\n * sections: [{ title: \"Docs\", links: [{ title: \"Packages\", url: \"https://lacspace.com/packages\" }] }],\n * });\n */\nexport function llmsTxt(doc: LlmsDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n if (doc.details) out.push(\"\", doc.details.trim());\n for (const section of doc.sections) {\n out.push(\"\", `## ${section.title}`, \"\");\n for (const l of section.links) {\n out.push(`- [${l.title}](${l.url})${l.notes ? `: ${l.notes}` : \"\"}`);\n }\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\nexport interface LlmsFullSection {\n title: string;\n /** Full Markdown content for this section. */\n content: string;\n /** Optional source URL, added as a heading link. */\n url?: string;\n}\n\nexport interface LlmsFullDoc {\n title: string;\n summary?: string;\n sections: LlmsFullSection[];\n}\n\n/** Render an `llms-full.txt` document with the full content inlined. */\nexport function llmsFullTxt(doc: LlmsFullDoc): string {\n const out: string[] = [`# ${doc.title}`];\n if (doc.summary) out.push(\"\", `> ${doc.summary}`);\n for (const section of doc.sections) {\n out.push(\"\", \"---\", \"\", `## ${section.title}`);\n if (section.url) out.push(\"\", `Source: ${section.url}`);\n out.push(\"\", section.content.trim());\n }\n return out.join(\"\\n\") + \"\\n\";\n}\n\n/** Parse an `llms.txt` string back into a structured document. */\nexport function parseLlmsTxt(txt: string): LlmsDoc {\n const lines = txt.split(/\\r?\\n/);\n const doc: LlmsDoc = { title: \"\", sections: [] };\n let current: LlmsSection | null = null;\n const detailBuf: string[] = [];\n const linkRe = /^-\\s*\\[([^\\]]+)\\]\\(([^)]+)\\)\\s*(?::\\s*(.*))?$/;\n\n for (const raw of lines) {\n const line = raw.trimEnd();\n if (line.startsWith(\"# \")) {\n doc.title = line.slice(2).trim();\n } else if (line.startsWith(\"> \")) {\n doc.summary = (doc.summary ? doc.summary + \" \" : \"\") + line.slice(2).trim();\n } else if (line.startsWith(\"## \")) {\n current = { title: line.slice(3).trim(), links: [] };\n doc.sections.push(current);\n } else if (current && linkRe.test(line)) {\n const m = line.match(linkRe)!;\n current.links.push({ title: m[1]!, url: m[2]!, notes: m[3]?.trim() || undefined });\n } else if (!current && line && !line.startsWith(\"#\")) {\n detailBuf.push(line);\n }\n }\n const details = detailBuf.join(\"\\n\").trim();\n if (details) doc.details = details;\n return doc;\n}\n\n/* ------------------------------ from sitemap ------------------------------ */\n\n/** A sitemap-ish entry — accepts `url` or `loc`, plus optional title/section. */\nexport interface SitemapEntryLike {\n url?: string;\n loc?: string;\n title?: string;\n section?: string;\n}\n\nfunction titleFromUrl(url: string): string {\n let path: string;\n try {\n path = new URL(url).pathname;\n } catch {\n path = url;\n }\n const seg = path.split(\"/\").filter(Boolean).pop();\n if (!seg) return \"Home\";\n let decoded: string;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n // Malformed percent-encoding — fall back to the raw segment instead of throwing.\n decoded = seg;\n }\n return decoded.replace(/\\.[a-z]+$/i, \"\").replace(/[-_]+/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Build an `llms.txt` from a list of sitemap entries — group by `section`,\n * derive titles from the URL when not given. Pairs with `@lacspace/sitemap`.\n */\nexport function llmsTxtFromSitemap(\n entries: SitemapEntryLike[],\n meta: { title: string; summary?: string; details?: string; defaultSection?: string },\n): string {\n const bySection = new Map<string, LlmsLink[]>();\n for (const e of entries) {\n const url = e.url ?? e.loc;\n if (!url) continue;\n const section = e.section ?? meta.defaultSection ?? \"Pages\";\n if (!bySection.has(section)) bySection.set(section, []);\n bySection.get(section)!.push({ title: e.title ?? titleFromUrl(url), url });\n }\n const doc: LlmsDoc = {\n title: meta.title,\n summary: meta.summary,\n details: meta.details,\n sections: [...bySection].map(([title, links]) => ({ title, links })),\n };\n return llmsTxt(doc);\n}\n\n/* ------------------------------ adapters ------------------------------ */\n\n/** `llms.txt` as a Fetch/edge `Response` (text/plain) for app/llms.txt/route.ts. */\nexport function llmsTxtResponse(doc: LlmsDoc, init: ResponseInit = {}): Response {\n return new Response(llmsTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n\n/** `llms-full.txt` as a Fetch/edge `Response` (text/plain). */\nexport function llmsFullTxtResponse(doc: LlmsFullDoc, init: ResponseInit = {}): Response {\n return new Response(llmsFullTxt(doc), {\n ...init,\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...(init.headers ?? {}) },\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/llms-txt",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Generate and parse llms.txt and llms-full.txt (the llmstxt.org standard) — a Markdown map of your site for LLMs. Zero-dependency, isomorphic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -35,7 +35,13 @@
|
|
|
35
35
|
"generative-engine-optimization",
|
|
36
36
|
"geo",
|
|
37
37
|
"seo",
|
|
38
|
-
"typescript"
|
|
38
|
+
"typescript",
|
|
39
|
+
"aeo",
|
|
40
|
+
"ai-search",
|
|
41
|
+
"chatgpt",
|
|
42
|
+
"perplexity",
|
|
43
|
+
"markdown",
|
|
44
|
+
"llms"
|
|
39
45
|
],
|
|
40
46
|
"author": "Lacspace <contact@lacspace.com>",
|
|
41
47
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -50,5 +56,8 @@
|
|
|
50
56
|
},
|
|
51
57
|
"engines": {
|
|
52
58
|
"node": ">=18"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
53
62
|
}
|
|
54
63
|
}
|