@lacspace/llms-txt 1.2.1 → 1.3.0
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 +42 -0
- package/dist/index.cjs +77 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -6
- package/dist/index.d.ts +60 -6
- package/dist/index.js +77 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,6 +120,48 @@ export function GET() {
|
|
|
120
120
|
}
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
## Advanced (new)
|
|
124
|
+
|
|
125
|
+
All additive and backward-compatible — `llmsTxt`, `llmsFullTxt`, `parseLlmsTxt`, `llmsTxtFromSitemap` and the `*Response` helpers keep their existing behavior.
|
|
126
|
+
|
|
127
|
+
### Build from routes
|
|
128
|
+
|
|
129
|
+
`llmsTxtFromRoutes(routes, meta)` turns a flat list of `{ title, url, notes?, section? }` into an `llms.txt`, grouping by `section` (first-seen order preserved).
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { llmsTxtFromRoutes } from "@lacspace/llms-txt";
|
|
133
|
+
|
|
134
|
+
llmsTxtFromRoutes(
|
|
135
|
+
[
|
|
136
|
+
{ title: "Home", url: "https://acme.com/", section: "Start" },
|
|
137
|
+
{ title: "API", url: "https://acme.com/api", notes: "reference", section: "Docs" },
|
|
138
|
+
{ title: "CLI", url: "https://acme.com/cli", section: "Docs" },
|
|
139
|
+
],
|
|
140
|
+
{ title: "Acme", summary: "Acme docs", defaultSection: "Docs" },
|
|
141
|
+
);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Sitemap: XML string, auto-sections, dedupe
|
|
145
|
+
|
|
146
|
+
`llmsTxtFromSitemap` now accepts a **raw sitemap XML string** as well as an array, de-duplicates repeated URLs, and can derive sections from the first path segment with `sectionFromPath: true`. The original array signature is unchanged.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
llmsTxtFromSitemap(sitemapXmlString, { title: "Acme", sectionFromPath: true });
|
|
150
|
+
// "/docs/intro" → "## Docs", "/blog/hello" → "## Blog"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Sort links within sections
|
|
154
|
+
|
|
155
|
+
`llmsTxt(doc, { sort })` — and the `sort` option on `llmsTxtFromRoutes` / `llmsTxtFromSitemap` — orders the links inside each section. Sections keep their array order.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
llmsTxt(doc, { sort: "title" }); // ascending by title
|
|
159
|
+
llmsTxt(doc, { sort: "url-desc" }); // descending by url
|
|
160
|
+
llmsTxt(doc, { sort: (a, b) => /* custom */ 0 });
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`parseLlmsTxt` round-trips what `llmsTxt` produces (title, summary, details, sections and links).
|
|
164
|
+
|
|
123
165
|
## Licensing
|
|
124
166
|
|
|
125
167
|
This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
|
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
function
|
|
4
|
+
function sortLinks(links, sort) {
|
|
5
|
+
if (!sort) return links;
|
|
6
|
+
const copy = links.slice();
|
|
7
|
+
if (typeof sort === "function") return copy.sort(sort);
|
|
8
|
+
const [field, dir] = sort.split("-");
|
|
9
|
+
const factor = dir === "desc" ? -1 : 1;
|
|
10
|
+
return copy.sort((a, b) => factor * a[field].localeCompare(b[field]));
|
|
11
|
+
}
|
|
12
|
+
function llmsTxt(doc, opts = {}) {
|
|
5
13
|
const out = [`# ${doc.title}`];
|
|
6
14
|
if (doc.summary) out.push("", `> ${doc.summary}`);
|
|
7
15
|
if (doc.details) out.push("", doc.details.trim());
|
|
8
16
|
for (const section of doc.sections) {
|
|
9
17
|
out.push("", `## ${section.title}`, "");
|
|
10
|
-
for (const l of section.links) {
|
|
18
|
+
for (const l of sortLinks(section.links, opts.sort)) {
|
|
11
19
|
out.push(`- [${l.title}](${l.url})${l.notes ? `: ${l.notes}` : ""}`);
|
|
12
20
|
}
|
|
13
21
|
}
|
|
@@ -58,24 +66,83 @@ function titleFromUrl(url) {
|
|
|
58
66
|
}
|
|
59
67
|
const seg = path.split("/").filter(Boolean).pop();
|
|
60
68
|
if (!seg) return "Home";
|
|
61
|
-
|
|
69
|
+
let decoded;
|
|
70
|
+
try {
|
|
71
|
+
decoded = decodeURIComponent(seg);
|
|
72
|
+
} catch {
|
|
73
|
+
decoded = seg;
|
|
74
|
+
}
|
|
75
|
+
return decoded.replace(/\.[a-z]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
76
|
+
}
|
|
77
|
+
function sectionFromUrl(url) {
|
|
78
|
+
let path;
|
|
79
|
+
try {
|
|
80
|
+
path = new URL(url).pathname;
|
|
81
|
+
} catch {
|
|
82
|
+
path = url;
|
|
83
|
+
}
|
|
84
|
+
const seg = path.split("/").filter(Boolean)[0];
|
|
85
|
+
if (!seg) return "Home";
|
|
86
|
+
let decoded;
|
|
87
|
+
try {
|
|
88
|
+
decoded = decodeURIComponent(seg);
|
|
89
|
+
} catch {
|
|
90
|
+
decoded = seg;
|
|
91
|
+
}
|
|
92
|
+
return decoded.replace(/\.[a-z]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
93
|
+
}
|
|
94
|
+
function parseSitemapXml(xml) {
|
|
95
|
+
const out = [];
|
|
96
|
+
const re = /<loc>\s*([\s\S]*?)\s*<\/loc>/gi;
|
|
97
|
+
let m;
|
|
98
|
+
while (m = re.exec(xml)) {
|
|
99
|
+
const loc = m[1].trim().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
100
|
+
if (loc) out.push({ loc });
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
62
103
|
}
|
|
63
104
|
function llmsTxtFromSitemap(entries, meta) {
|
|
105
|
+
const list = typeof entries === "string" ? parseSitemapXml(entries) : entries;
|
|
64
106
|
const bySection = /* @__PURE__ */ new Map();
|
|
65
|
-
|
|
107
|
+
const order = [];
|
|
108
|
+
const seen = /* @__PURE__ */ new Set();
|
|
109
|
+
for (const e of list) {
|
|
66
110
|
const url = e.url ?? e.loc;
|
|
67
|
-
if (!url) continue;
|
|
68
|
-
|
|
69
|
-
|
|
111
|
+
if (!url || seen.has(url)) continue;
|
|
112
|
+
seen.add(url);
|
|
113
|
+
const section = e.section ?? (meta.sectionFromPath ? sectionFromUrl(url) : meta.defaultSection ?? "Pages");
|
|
114
|
+
if (!bySection.has(section)) {
|
|
115
|
+
bySection.set(section, []);
|
|
116
|
+
order.push(section);
|
|
117
|
+
}
|
|
70
118
|
bySection.get(section).push({ title: e.title ?? titleFromUrl(url), url });
|
|
71
119
|
}
|
|
72
120
|
const doc = {
|
|
73
121
|
title: meta.title,
|
|
74
122
|
summary: meta.summary,
|
|
75
123
|
details: meta.details,
|
|
76
|
-
sections:
|
|
124
|
+
sections: order.map((title) => ({ title, links: bySection.get(title) }))
|
|
125
|
+
};
|
|
126
|
+
return llmsTxt(doc, { sort: meta.sort });
|
|
127
|
+
}
|
|
128
|
+
function llmsTxtFromRoutes(routes, meta) {
|
|
129
|
+
const bySection = /* @__PURE__ */ new Map();
|
|
130
|
+
const order = [];
|
|
131
|
+
for (const r of routes) {
|
|
132
|
+
const section = r.section ?? meta.defaultSection ?? "Docs";
|
|
133
|
+
if (!bySection.has(section)) {
|
|
134
|
+
bySection.set(section, []);
|
|
135
|
+
order.push(section);
|
|
136
|
+
}
|
|
137
|
+
bySection.get(section).push({ title: r.title, url: r.url, notes: r.notes });
|
|
138
|
+
}
|
|
139
|
+
const doc = {
|
|
140
|
+
title: meta.title,
|
|
141
|
+
summary: meta.summary,
|
|
142
|
+
details: meta.details,
|
|
143
|
+
sections: order.map((title) => ({ title, links: bySection.get(title) }))
|
|
77
144
|
};
|
|
78
|
-
return llmsTxt(doc);
|
|
145
|
+
return llmsTxt(doc, { sort: meta.sort });
|
|
79
146
|
}
|
|
80
147
|
function llmsTxtResponse(doc, init = {}) {
|
|
81
148
|
return new Response(llmsTxt(doc), {
|
|
@@ -93,6 +160,7 @@ function llmsFullTxtResponse(doc, init = {}) {
|
|
|
93
160
|
exports.llmsFullTxt = llmsFullTxt;
|
|
94
161
|
exports.llmsFullTxtResponse = llmsFullTxtResponse;
|
|
95
162
|
exports.llmsTxt = llmsTxt;
|
|
163
|
+
exports.llmsTxtFromRoutes = llmsTxtFromRoutes;
|
|
96
164
|
exports.llmsTxtFromSitemap = llmsTxtFromSitemap;
|
|
97
165
|
exports.llmsTxtResponse = llmsTxtResponse;
|
|
98
166
|
exports.parseLlmsTxt = parseLlmsTxt;
|
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":";;;AAqDA,SAAS,SAAA,CAAU,OAAmB,IAAA,EAA6B;AACjE,EAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,EAAA,MAAM,IAAA,GAAO,MAAM,KAAA,EAAM;AACzB,EAAA,IAAI,OAAO,IAAA,KAAS,UAAA,EAAY,OAAO,IAAA,CAAK,KAAK,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,KAAA,EAAO,GAAG,CAAA,GAAI,IAAA,CAAK,MAAM,GAAG,CAAA;AACnC,EAAA,MAAM,MAAA,GAAS,GAAA,KAAQ,MAAA,GAAS,EAAA,GAAK,CAAA;AACrC,EAAA,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,MAAA,GAAS,CAAA,CAAE,KAAK,CAAA,CAAE,aAAA,CAAc,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AACtE;AAYO,SAAS,OAAA,CAAQ,GAAA,EAAc,IAAA,GAAuB,EAAC,EAAW;AACvE,EAAA,MAAM,GAAA,GAAgB,CAAC,CAAA,EAAA,EAAK,GAAA,CAAI,KAAK,CAAA,CAAE,CAAA;AACvC,EAAA,IAAI,GAAA,CAAI,SAAS,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA;AAChD,EAAA,IAAI,GAAA,CAAI,SAAS,GAAA,CAAI,IAAA,CAAK,IAAI,GAAA,CAAI,OAAA,CAAQ,MAAM,CAAA;AAChD,EAAA,KAAA,MAAW,OAAA,IAAW,IAAI,QAAA,EAAU;AAClC,IAAA,GAAA,CAAI,KAAK,EAAA,EAAI,CAAA,GAAA,EAAM,OAAA,CAAQ,KAAK,IAAI,EAAE,CAAA;AACtC,IAAA,KAAA,MAAW,KAAK,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACnD,MAAA,GAAA,CAAI,IAAA,CAAK,CAAA,GAAA,EAAM,CAAA,CAAE,KAAK,KAAK,CAAA,CAAE,GAAG,CAAA,CAAA,EAAI,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK,CAAA,CAAE,KAAK,CAAA,CAAA,GAAK,EAAE,CAAA,CAAE,CAAA;AAAA,IACrE;AAAA,EACF;AACA,EAAA,OAAO,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AAC1B;AAiBO,SAAS,YAAY,GAAA,EAA0B;AACpD,EAAA,MAAM,GAAA,GAAgB,CAAC,CAAA,EAAA,EAAK,GAAA,CAAI,KAAK,CAAA,CAAE,CAAA;AACvC,EAAA,IAAI,GAAA,CAAI,SAAS,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA;AAChD,EAAA,KAAA,MAAW,OAAA,IAAW,IAAI,QAAA,EAAU;AAClC,IAAA,GAAA,CAAI,KAAK,EAAA,EAAI,KAAA,EAAO,IAAI,CAAA,GAAA,EAAM,OAAA,CAAQ,KAAK,CAAA,CAAE,CAAA;AAC7C,IAAA,IAAI,OAAA,CAAQ,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,QAAA,EAAW,OAAA,CAAQ,GAAG,CAAA,CAAE,CAAA;AACtD,IAAA,GAAA,CAAI,IAAA,CAAK,EAAA,EAAI,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AAC1B;AAGO,SAAS,aAAa,GAAA,EAAsB;AACjD,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AAC/B,EAAA,MAAM,MAAe,EAAE,KAAA,EAAO,EAAA,EAAI,QAAA,EAAU,EAAC,EAAE;AAC/C,EAAA,IAAI,OAAA,GAA8B,IAAA;AAClC,EAAA,MAAM,YAAsB,EAAC;AAC7B,EAAA,MAAM,MAAA,GAAS,+CAAA;AAEf,EAAA,KAAA,MAAW,OAAO,KAAA,EAAO;AACvB,IAAA,MAAM,IAAA,GAAO,IAAI,OAAA,EAAQ;AACzB,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,IAAI,CAAA,EAAG;AACzB,MAAA,GAAA,CAAI,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,EAAE,IAAA,EAAK;AAAA,IACjC,CAAA,MAAA,IAAW,IAAA,CAAK,UAAA,CAAW,IAAI,CAAA,EAAG;AAChC,MAAA,GAAA,CAAI,OAAA,GAAA,CAAW,GAAA,CAAI,OAAA,GAAU,GAAA,CAAI,OAAA,GAAU,GAAA,GAAM,EAAA,IAAM,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,IAC5E,CAAA,MAAA,IAAW,IAAA,CAAK,UAAA,CAAW,KAAK,CAAA,EAAG;AACjC,MAAA,OAAA,GAAU,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,CAAC,EAAE,IAAA,EAAK,EAAG,KAAA,EAAO,EAAC,EAAE;AACnD,MAAA,GAAA,CAAI,QAAA,CAAS,KAAK,OAAO,CAAA;AAAA,IAC3B,CAAA,MAAA,IAAW,OAAA,IAAW,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACvC,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAC3B,MAAA,OAAA,CAAQ,MAAM,IAAA,CAAK,EAAE,OAAO,CAAA,CAAE,CAAC,GAAI,GAAA,EAAK,CAAA,CAAE,CAAC,CAAA,EAAI,OAAO,CAAA,CAAE,CAAC,GAAG,IAAA,EAAK,IAAK,QAAW,CAAA;AAAA,IACnF,CAAA,MAAA,IAAW,CAAC,OAAA,IAAW,IAAA,IAAQ,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AACpD,MAAA,SAAA,CAAU,KAAK,IAAI,CAAA;AAAA,IACrB;AAAA,EACF;AACA,EAAA,MAAM,OAAA,GAAU,SAAA,CAAU,IAAA,CAAK,IAAI,EAAE,IAAA,EAAK;AAC1C,EAAA,IAAI,OAAA,MAAa,OAAA,GAAU,OAAA;AAC3B,EAAA,OAAO,GAAA;AACT;AAYA,SAAS,aAAa,GAAA,EAAqB;AACzC,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,GAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,QAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AACN,IAAA,IAAA,GAAO,GAAA;AAAA,EACT;AACA,EAAA,MAAM,GAAA,GAAM,KAAK,KAAA,CAAM,GAAG,EAAE,MAAA,CAAO,OAAO,EAAE,GAAA,EAAI;AAChD,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACF,IAAA,OAAA,GAAU,mBAAmB,GAAG,CAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AAEN,IAAA,OAAA,GAAU,GAAA;AAAA,EACZ;AACA,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,YAAA,EAAc,EAAE,EAAE,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA,CAAE,QAAQ,OAAA,EAAS,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AACzG;AAGA,SAAS,eAAe,GAAA,EAAqB;AAC3C,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,GAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,QAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AACN,IAAA,IAAA,GAAO,GAAA;AAAA,EACT;AACA,EAAA,MAAM,GAAA,GAAM,KAAK,KAAA,CAAM,GAAG,EAAE,MAAA,CAAO,OAAO,EAAE,CAAC,CAAA;AAC7C,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACF,IAAA,OAAA,GAAU,mBAAmB,GAAG,CAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAA,GAAU,GAAA;AAAA,EACZ;AACA,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,YAAA,EAAc,EAAE,EAAE,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA,CAAE,QAAQ,OAAA,EAAS,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AACzG;AAGA,SAAS,gBAAgB,GAAA,EAAiC;AACxD,EAAA,MAAM,MAA0B,EAAC;AACjC,EAAA,MAAM,EAAA,GAAK,gCAAA;AACX,EAAA,IAAI,CAAA;AACJ,EAAA,OAAQ,CAAA,GAAI,EAAA,CAAG,IAAA,CAAK,GAAG,CAAA,EAAI;AACzB,IAAA,MAAM,GAAA,GAAM,CAAA,CAAE,CAAC,CAAA,CACZ,IAAA,GACA,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA,CACrB,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA,CACpB,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA,CACpB,OAAA,CAAQ,WAAW,GAAG,CAAA,CACtB,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA;AACzB,IAAA,IAAI,GAAA,EAAK,GAAA,CAAI,IAAA,CAAK,EAAE,KAAK,CAAA;AAAA,EAC3B;AACA,EAAA,OAAO,GAAA;AACT;AAuBO,SAAS,kBAAA,CACd,SACA,IAAA,EACQ;AACR,EAAA,MAAM,OAAO,OAAO,OAAA,KAAY,QAAA,GAAW,eAAA,CAAgB,OAAO,CAAA,GAAI,OAAA;AACtE,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAwB;AAC9C,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,KAAK,IAAA,EAAM;AACpB,IAAA,MAAM,GAAA,GAAM,CAAA,CAAE,GAAA,IAAO,CAAA,CAAE,GAAA;AACvB,IAAA,IAAI,CAAC,GAAA,IAAO,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG;AAC3B,IAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,IAAA,MAAM,OAAA,GACJ,EAAE,OAAA,KAAY,IAAA,CAAK,kBAAkB,cAAA,CAAe,GAAG,CAAA,GAAI,IAAA,CAAK,cAAA,IAAkB,OAAA,CAAA;AACpF,IAAA,IAAI,CAAC,SAAA,CAAU,GAAA,CAAI,OAAO,CAAA,EAAG;AAC3B,MAAA,SAAA,CAAU,GAAA,CAAI,OAAA,EAAS,EAAE,CAAA;AACzB,MAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,IACpB;AACA,IAAA,SAAA,CAAU,GAAA,CAAI,OAAO,CAAA,CAAG,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,IAAS,YAAA,CAAa,GAAG,CAAA,EAAG,GAAA,EAAK,CAAA;AAAA,EAC3E;AACA,EAAA,MAAM,GAAA,GAAe;AAAA,IACnB,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,QAAA,EAAU,KAAA,CAAM,GAAA,CAAI,CAAC,KAAA,MAAW,EAAE,KAAA,EAAO,KAAA,EAAO,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,CAAE;AAAA,GAC1E;AACA,EAAA,OAAO,QAAQ,GAAA,EAAK,EAAE,IAAA,EAAM,IAAA,CAAK,MAAM,CAAA;AACzC;AAkCO,SAAS,iBAAA,CAAkB,QAAqB,IAAA,EAAgC;AACrF,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAwB;AAC9C,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,IAAA,MAAM,OAAA,GAAU,CAAA,CAAE,OAAA,IAAW,IAAA,CAAK,cAAA,IAAkB,MAAA;AACpD,IAAA,IAAI,CAAC,SAAA,CAAU,GAAA,CAAI,OAAO,CAAA,EAAG;AAC3B,MAAA,SAAA,CAAU,GAAA,CAAI,OAAA,EAAS,EAAE,CAAA;AACzB,MAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,IACpB;AACA,IAAA,SAAA,CAAU,GAAA,CAAI,OAAO,CAAA,CAAG,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAO,GAAA,EAAK,CAAA,CAAE,GAAA,EAAK,KAAA,EAAO,CAAA,CAAE,OAAO,CAAA;AAAA,EAC7E;AACA,EAAA,MAAM,GAAA,GAAe;AAAA,IACnB,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,QAAA,EAAU,KAAA,CAAM,GAAA,CAAI,CAAC,KAAA,MAAW,EAAE,KAAA,EAAO,KAAA,EAAO,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,CAAE;AAAA,GAC1E;AACA,EAAA,OAAO,QAAQ,GAAA,EAAK,EAAE,IAAA,EAAM,IAAA,CAAK,MAAM,CAAA;AACzC;AAKO,SAAS,eAAA,CAAgB,GAAA,EAAc,IAAA,GAAqB,EAAC,EAAa;AAC/E,EAAA,OAAO,IAAI,QAAA,CAAS,OAAA,CAAQ,GAAG,CAAA,EAAG;AAAA,IAChC,GAAG,IAAA;AAAA,IACH,OAAA,EAAS,EAAE,cAAA,EAAgB,2BAAA,EAA6B,GAAI,IAAA,CAAK,OAAA,IAAW,EAAC;AAAG,GACjF,CAAA;AACH;AAGO,SAAS,mBAAA,CAAoB,GAAA,EAAkB,IAAA,GAAqB,EAAC,EAAa;AACvF,EAAA,OAAO,IAAI,QAAA,CAAS,WAAA,CAAY,GAAG,CAAA,EAAG;AAAA,IACpC,GAAG,IAAA;AAAA,IACH,OAAA,EAAS,EAAE,cAAA,EAAgB,2BAAA,EAA6B,GAAI,IAAA,CAAK,OAAA,IAAW,EAAC;AAAG,GACjF,CAAA;AACH","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 * How to order the links inside each section.\n * - `\"title\"` / `\"url\"` — ascending by that field\n * - `\"title-desc\"` / `\"url-desc\"` — descending\n * - a comparator — full control\n * Sections keep their array order; only the links within a section are sorted.\n */\nexport type LinkSort =\n | \"title\"\n | \"url\"\n | \"title-desc\"\n | \"url-desc\"\n | ((a: LlmsLink, b: LlmsLink) => number);\n\n/** Options accepted by the rendering helpers. */\nexport interface LlmsTxtOptions {\n /** Order links within each section. Omit to keep the given order. */\n sort?: LinkSort;\n}\n\nfunction sortLinks(links: LlmsLink[], sort?: LinkSort): LlmsLink[] {\n if (!sort) return links;\n const copy = links.slice();\n if (typeof sort === \"function\") return copy.sort(sort);\n const [field, dir] = sort.split(\"-\") as [\"title\" | \"url\", \"desc\" | undefined];\n const factor = dir === \"desc\" ? -1 : 1;\n return copy.sort((a, b) => factor * a[field].localeCompare(b[field]));\n}\n\n/**\n * Render an `llms.txt` document.\n * @param opts optional rendering options (e.g. `{ sort: \"title\" }`).\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, opts: LlmsTxtOptions = {}): 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 sortLinks(section.links, opts.sort)) {\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/** First path segment of a URL, title-cased, for auto-sectioning (\"/docs/x\" → \"Docs\"). */\nfunction sectionFromUrl(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)[0];\n if (!seg) return \"Home\";\n let decoded: string;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n decoded = seg;\n }\n return decoded.replace(/\\.[a-z]+$/i, \"\").replace(/[-_]+/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/** Pull `<loc>` values out of a sitemap (or sitemap-index) XML string. */\nfunction parseSitemapXml(xml: string): SitemapEntryLike[] {\n const out: SitemapEntryLike[] = [];\n const re = /<loc>\\s*([\\s\\S]*?)\\s*<\\/loc>/gi;\n let m: RegExpExecArray | null;\n while ((m = re.exec(xml))) {\n const loc = m[1]!\n .trim()\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\");\n if (loc) out.push({ loc });\n }\n return out;\n}\n\n/** Meta / options for {@link llmsTxtFromSitemap}. */\nexport interface SitemapToLlmsMeta extends LlmsTxtOptions {\n title: string;\n summary?: string;\n details?: string;\n /** Fallback section name when an entry has none. Default `\"Pages\"`. */\n defaultSection?: string;\n /**\n * When an entry has no explicit `section`, derive one from the first path\n * segment (\"/docs/x\" → \"Docs\") instead of using {@link defaultSection}.\n */\n sectionFromPath?: boolean;\n}\n\n/**\n * Build an `llms.txt` from sitemap entries — accepts either an **array** of\n * `{ loc | url, title?, section? }` entries or a raw **sitemap XML string**.\n * Groups by `section` (or, with `sectionFromPath`, by the first path segment),\n * derives titles from the URL when not given, and de-duplicates repeated URLs.\n * Pairs with `@lacspace/sitemap`. The original array signature is unchanged.\n */\nexport function llmsTxtFromSitemap(\n entries: SitemapEntryLike[] | string,\n meta: SitemapToLlmsMeta,\n): string {\n const list = typeof entries === \"string\" ? parseSitemapXml(entries) : entries;\n const bySection = new Map<string, LlmsLink[]>();\n const order: string[] = [];\n const seen = new Set<string>();\n for (const e of list) {\n const url = e.url ?? e.loc;\n if (!url || seen.has(url)) continue;\n seen.add(url);\n const section =\n e.section ?? (meta.sectionFromPath ? sectionFromUrl(url) : meta.defaultSection ?? \"Pages\");\n if (!bySection.has(section)) {\n bySection.set(section, []);\n order.push(section);\n }\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: order.map((title) => ({ title, links: bySection.get(title)! })),\n };\n return llmsTxt(doc, { sort: meta.sort });\n}\n\n/* ------------------------------ from routes ------------------------------ */\n\n/** A single route/page entry for {@link llmsTxtFromRoutes}. */\nexport interface LlmsRoute {\n title: string;\n url: string;\n notes?: string;\n /** Section heading to group under. Defaults to {@link RoutesToLlmsMeta.defaultSection}. */\n section?: string;\n}\n\n/** Meta / options for {@link llmsTxtFromRoutes}. */\nexport interface RoutesToLlmsMeta extends LlmsTxtOptions {\n title: string;\n summary?: string;\n details?: string;\n /** Section name for routes with no `section`. Default `\"Docs\"`. */\n defaultSection?: string;\n}\n\n/**\n * Build an `llms.txt` from a flat list of route entries, grouping by `section`\n * (first-seen order preserved). Titles and notes are used verbatim.\n * @example\n * llmsTxtFromRoutes(\n * [\n * { title: \"Home\", url: \"https://acme.com/\", section: \"Start\" },\n * { title: \"API\", url: \"https://acme.com/api\", notes: \"reference\", section: \"Docs\" },\n * ],\n * { title: \"Acme\", summary: \"Acme docs\" },\n * );\n */\nexport function llmsTxtFromRoutes(routes: LlmsRoute[], meta: RoutesToLlmsMeta): string {\n const bySection = new Map<string, LlmsLink[]>();\n const order: string[] = [];\n for (const r of routes) {\n const section = r.section ?? meta.defaultSection ?? \"Docs\";\n if (!bySection.has(section)) {\n bySection.set(section, []);\n order.push(section);\n }\n bySection.get(section)!.push({ title: r.title, url: r.url, notes: r.notes });\n }\n const doc: LlmsDoc = {\n title: meta.title,\n summary: meta.summary,\n details: meta.details,\n sections: order.map((title) => ({ title, links: bySection.get(title)! })),\n };\n return llmsTxt(doc, { sort: meta.sort });\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.d.cts
CHANGED
|
@@ -27,8 +27,22 @@ interface LlmsDoc {
|
|
|
27
27
|
details?: string;
|
|
28
28
|
sections: LlmsSection[];
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* How to order the links inside each section.
|
|
32
|
+
* - `"title"` / `"url"` — ascending by that field
|
|
33
|
+
* - `"title-desc"` / `"url-desc"` — descending
|
|
34
|
+
* - a comparator — full control
|
|
35
|
+
* Sections keep their array order; only the links within a section are sorted.
|
|
36
|
+
*/
|
|
37
|
+
type LinkSort = "title" | "url" | "title-desc" | "url-desc" | ((a: LlmsLink, b: LlmsLink) => number);
|
|
38
|
+
/** Options accepted by the rendering helpers. */
|
|
39
|
+
interface LlmsTxtOptions {
|
|
40
|
+
/** Order links within each section. Omit to keep the given order. */
|
|
41
|
+
sort?: LinkSort;
|
|
42
|
+
}
|
|
30
43
|
/**
|
|
31
44
|
* Render an `llms.txt` document.
|
|
45
|
+
* @param opts optional rendering options (e.g. `{ sort: "title" }`).
|
|
32
46
|
* @example
|
|
33
47
|
* llmsTxt({
|
|
34
48
|
* title: "Lacspace",
|
|
@@ -36,7 +50,7 @@ interface LlmsDoc {
|
|
|
36
50
|
* sections: [{ title: "Docs", links: [{ title: "Packages", url: "https://lacspace.com/packages" }] }],
|
|
37
51
|
* });
|
|
38
52
|
*/
|
|
39
|
-
declare function llmsTxt(doc: LlmsDoc): string;
|
|
53
|
+
declare function llmsTxt(doc: LlmsDoc, opts?: LlmsTxtOptions): string;
|
|
40
54
|
interface LlmsFullSection {
|
|
41
55
|
title: string;
|
|
42
56
|
/** Full Markdown content for this section. */
|
|
@@ -60,19 +74,59 @@ interface SitemapEntryLike {
|
|
|
60
74
|
title?: string;
|
|
61
75
|
section?: string;
|
|
62
76
|
}
|
|
77
|
+
/** Meta / options for {@link llmsTxtFromSitemap}. */
|
|
78
|
+
interface SitemapToLlmsMeta extends LlmsTxtOptions {
|
|
79
|
+
title: string;
|
|
80
|
+
summary?: string;
|
|
81
|
+
details?: string;
|
|
82
|
+
/** Fallback section name when an entry has none. Default `"Pages"`. */
|
|
83
|
+
defaultSection?: string;
|
|
84
|
+
/**
|
|
85
|
+
* When an entry has no explicit `section`, derive one from the first path
|
|
86
|
+
* segment ("/docs/x" → "Docs") instead of using {@link defaultSection}.
|
|
87
|
+
*/
|
|
88
|
+
sectionFromPath?: boolean;
|
|
89
|
+
}
|
|
63
90
|
/**
|
|
64
|
-
* Build an `llms.txt` from
|
|
65
|
-
*
|
|
91
|
+
* Build an `llms.txt` from sitemap entries — accepts either an **array** of
|
|
92
|
+
* `{ loc | url, title?, section? }` entries or a raw **sitemap XML string**.
|
|
93
|
+
* Groups by `section` (or, with `sectionFromPath`, by the first path segment),
|
|
94
|
+
* derives titles from the URL when not given, and de-duplicates repeated URLs.
|
|
95
|
+
* Pairs with `@lacspace/sitemap`. The original array signature is unchanged.
|
|
66
96
|
*/
|
|
67
|
-
declare function llmsTxtFromSitemap(entries: SitemapEntryLike[], meta:
|
|
97
|
+
declare function llmsTxtFromSitemap(entries: SitemapEntryLike[] | string, meta: SitemapToLlmsMeta): string;
|
|
98
|
+
/** A single route/page entry for {@link llmsTxtFromRoutes}. */
|
|
99
|
+
interface LlmsRoute {
|
|
100
|
+
title: string;
|
|
101
|
+
url: string;
|
|
102
|
+
notes?: string;
|
|
103
|
+
/** Section heading to group under. Defaults to {@link RoutesToLlmsMeta.defaultSection}. */
|
|
104
|
+
section?: string;
|
|
105
|
+
}
|
|
106
|
+
/** Meta / options for {@link llmsTxtFromRoutes}. */
|
|
107
|
+
interface RoutesToLlmsMeta extends LlmsTxtOptions {
|
|
68
108
|
title: string;
|
|
69
109
|
summary?: string;
|
|
70
110
|
details?: string;
|
|
111
|
+
/** Section name for routes with no `section`. Default `"Docs"`. */
|
|
71
112
|
defaultSection?: string;
|
|
72
|
-
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build an `llms.txt` from a flat list of route entries, grouping by `section`
|
|
116
|
+
* (first-seen order preserved). Titles and notes are used verbatim.
|
|
117
|
+
* @example
|
|
118
|
+
* llmsTxtFromRoutes(
|
|
119
|
+
* [
|
|
120
|
+
* { title: "Home", url: "https://acme.com/", section: "Start" },
|
|
121
|
+
* { title: "API", url: "https://acme.com/api", notes: "reference", section: "Docs" },
|
|
122
|
+
* ],
|
|
123
|
+
* { title: "Acme", summary: "Acme docs" },
|
|
124
|
+
* );
|
|
125
|
+
*/
|
|
126
|
+
declare function llmsTxtFromRoutes(routes: LlmsRoute[], meta: RoutesToLlmsMeta): string;
|
|
73
127
|
/** `llms.txt` as a Fetch/edge `Response` (text/plain) for app/llms.txt/route.ts. */
|
|
74
128
|
declare function llmsTxtResponse(doc: LlmsDoc, init?: ResponseInit): Response;
|
|
75
129
|
/** `llms-full.txt` as a Fetch/edge `Response` (text/plain). */
|
|
76
130
|
declare function llmsFullTxtResponse(doc: LlmsFullDoc, init?: ResponseInit): Response;
|
|
77
131
|
|
|
78
|
-
export { type LlmsDoc, type LlmsFullDoc, type LlmsFullSection, type LlmsLink, type LlmsSection, type SitemapEntryLike, llmsFullTxt, llmsFullTxtResponse, llmsTxt, llmsTxtFromSitemap, llmsTxtResponse, parseLlmsTxt };
|
|
132
|
+
export { type LinkSort, type LlmsDoc, type LlmsFullDoc, type LlmsFullSection, type LlmsLink, type LlmsRoute, type LlmsSection, type LlmsTxtOptions, type RoutesToLlmsMeta, type SitemapEntryLike, type SitemapToLlmsMeta, llmsFullTxt, llmsFullTxtResponse, llmsTxt, llmsTxtFromRoutes, llmsTxtFromSitemap, llmsTxtResponse, parseLlmsTxt };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,8 +27,22 @@ interface LlmsDoc {
|
|
|
27
27
|
details?: string;
|
|
28
28
|
sections: LlmsSection[];
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* How to order the links inside each section.
|
|
32
|
+
* - `"title"` / `"url"` — ascending by that field
|
|
33
|
+
* - `"title-desc"` / `"url-desc"` — descending
|
|
34
|
+
* - a comparator — full control
|
|
35
|
+
* Sections keep their array order; only the links within a section are sorted.
|
|
36
|
+
*/
|
|
37
|
+
type LinkSort = "title" | "url" | "title-desc" | "url-desc" | ((a: LlmsLink, b: LlmsLink) => number);
|
|
38
|
+
/** Options accepted by the rendering helpers. */
|
|
39
|
+
interface LlmsTxtOptions {
|
|
40
|
+
/** Order links within each section. Omit to keep the given order. */
|
|
41
|
+
sort?: LinkSort;
|
|
42
|
+
}
|
|
30
43
|
/**
|
|
31
44
|
* Render an `llms.txt` document.
|
|
45
|
+
* @param opts optional rendering options (e.g. `{ sort: "title" }`).
|
|
32
46
|
* @example
|
|
33
47
|
* llmsTxt({
|
|
34
48
|
* title: "Lacspace",
|
|
@@ -36,7 +50,7 @@ interface LlmsDoc {
|
|
|
36
50
|
* sections: [{ title: "Docs", links: [{ title: "Packages", url: "https://lacspace.com/packages" }] }],
|
|
37
51
|
* });
|
|
38
52
|
*/
|
|
39
|
-
declare function llmsTxt(doc: LlmsDoc): string;
|
|
53
|
+
declare function llmsTxt(doc: LlmsDoc, opts?: LlmsTxtOptions): string;
|
|
40
54
|
interface LlmsFullSection {
|
|
41
55
|
title: string;
|
|
42
56
|
/** Full Markdown content for this section. */
|
|
@@ -60,19 +74,59 @@ interface SitemapEntryLike {
|
|
|
60
74
|
title?: string;
|
|
61
75
|
section?: string;
|
|
62
76
|
}
|
|
77
|
+
/** Meta / options for {@link llmsTxtFromSitemap}. */
|
|
78
|
+
interface SitemapToLlmsMeta extends LlmsTxtOptions {
|
|
79
|
+
title: string;
|
|
80
|
+
summary?: string;
|
|
81
|
+
details?: string;
|
|
82
|
+
/** Fallback section name when an entry has none. Default `"Pages"`. */
|
|
83
|
+
defaultSection?: string;
|
|
84
|
+
/**
|
|
85
|
+
* When an entry has no explicit `section`, derive one from the first path
|
|
86
|
+
* segment ("/docs/x" → "Docs") instead of using {@link defaultSection}.
|
|
87
|
+
*/
|
|
88
|
+
sectionFromPath?: boolean;
|
|
89
|
+
}
|
|
63
90
|
/**
|
|
64
|
-
* Build an `llms.txt` from
|
|
65
|
-
*
|
|
91
|
+
* Build an `llms.txt` from sitemap entries — accepts either an **array** of
|
|
92
|
+
* `{ loc | url, title?, section? }` entries or a raw **sitemap XML string**.
|
|
93
|
+
* Groups by `section` (or, with `sectionFromPath`, by the first path segment),
|
|
94
|
+
* derives titles from the URL when not given, and de-duplicates repeated URLs.
|
|
95
|
+
* Pairs with `@lacspace/sitemap`. The original array signature is unchanged.
|
|
66
96
|
*/
|
|
67
|
-
declare function llmsTxtFromSitemap(entries: SitemapEntryLike[], meta:
|
|
97
|
+
declare function llmsTxtFromSitemap(entries: SitemapEntryLike[] | string, meta: SitemapToLlmsMeta): string;
|
|
98
|
+
/** A single route/page entry for {@link llmsTxtFromRoutes}. */
|
|
99
|
+
interface LlmsRoute {
|
|
100
|
+
title: string;
|
|
101
|
+
url: string;
|
|
102
|
+
notes?: string;
|
|
103
|
+
/** Section heading to group under. Defaults to {@link RoutesToLlmsMeta.defaultSection}. */
|
|
104
|
+
section?: string;
|
|
105
|
+
}
|
|
106
|
+
/** Meta / options for {@link llmsTxtFromRoutes}. */
|
|
107
|
+
interface RoutesToLlmsMeta extends LlmsTxtOptions {
|
|
68
108
|
title: string;
|
|
69
109
|
summary?: string;
|
|
70
110
|
details?: string;
|
|
111
|
+
/** Section name for routes with no `section`. Default `"Docs"`. */
|
|
71
112
|
defaultSection?: string;
|
|
72
|
-
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build an `llms.txt` from a flat list of route entries, grouping by `section`
|
|
116
|
+
* (first-seen order preserved). Titles and notes are used verbatim.
|
|
117
|
+
* @example
|
|
118
|
+
* llmsTxtFromRoutes(
|
|
119
|
+
* [
|
|
120
|
+
* { title: "Home", url: "https://acme.com/", section: "Start" },
|
|
121
|
+
* { title: "API", url: "https://acme.com/api", notes: "reference", section: "Docs" },
|
|
122
|
+
* ],
|
|
123
|
+
* { title: "Acme", summary: "Acme docs" },
|
|
124
|
+
* );
|
|
125
|
+
*/
|
|
126
|
+
declare function llmsTxtFromRoutes(routes: LlmsRoute[], meta: RoutesToLlmsMeta): string;
|
|
73
127
|
/** `llms.txt` as a Fetch/edge `Response` (text/plain) for app/llms.txt/route.ts. */
|
|
74
128
|
declare function llmsTxtResponse(doc: LlmsDoc, init?: ResponseInit): Response;
|
|
75
129
|
/** `llms-full.txt` as a Fetch/edge `Response` (text/plain). */
|
|
76
130
|
declare function llmsFullTxtResponse(doc: LlmsFullDoc, init?: ResponseInit): Response;
|
|
77
131
|
|
|
78
|
-
export { type LlmsDoc, type LlmsFullDoc, type LlmsFullSection, type LlmsLink, type LlmsSection, type SitemapEntryLike, llmsFullTxt, llmsFullTxtResponse, llmsTxt, llmsTxtFromSitemap, llmsTxtResponse, parseLlmsTxt };
|
|
132
|
+
export { type LinkSort, type LlmsDoc, type LlmsFullDoc, type LlmsFullSection, type LlmsLink, type LlmsRoute, type LlmsSection, type LlmsTxtOptions, type RoutesToLlmsMeta, type SitemapEntryLike, type SitemapToLlmsMeta, llmsFullTxt, llmsFullTxtResponse, llmsTxt, llmsTxtFromRoutes, llmsTxtFromSitemap, llmsTxtResponse, parseLlmsTxt };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
function
|
|
2
|
+
function sortLinks(links, sort) {
|
|
3
|
+
if (!sort) return links;
|
|
4
|
+
const copy = links.slice();
|
|
5
|
+
if (typeof sort === "function") return copy.sort(sort);
|
|
6
|
+
const [field, dir] = sort.split("-");
|
|
7
|
+
const factor = dir === "desc" ? -1 : 1;
|
|
8
|
+
return copy.sort((a, b) => factor * a[field].localeCompare(b[field]));
|
|
9
|
+
}
|
|
10
|
+
function llmsTxt(doc, opts = {}) {
|
|
3
11
|
const out = [`# ${doc.title}`];
|
|
4
12
|
if (doc.summary) out.push("", `> ${doc.summary}`);
|
|
5
13
|
if (doc.details) out.push("", doc.details.trim());
|
|
6
14
|
for (const section of doc.sections) {
|
|
7
15
|
out.push("", `## ${section.title}`, "");
|
|
8
|
-
for (const l of section.links) {
|
|
16
|
+
for (const l of sortLinks(section.links, opts.sort)) {
|
|
9
17
|
out.push(`- [${l.title}](${l.url})${l.notes ? `: ${l.notes}` : ""}`);
|
|
10
18
|
}
|
|
11
19
|
}
|
|
@@ -56,24 +64,83 @@ function titleFromUrl(url) {
|
|
|
56
64
|
}
|
|
57
65
|
const seg = path.split("/").filter(Boolean).pop();
|
|
58
66
|
if (!seg) return "Home";
|
|
59
|
-
|
|
67
|
+
let decoded;
|
|
68
|
+
try {
|
|
69
|
+
decoded = decodeURIComponent(seg);
|
|
70
|
+
} catch {
|
|
71
|
+
decoded = seg;
|
|
72
|
+
}
|
|
73
|
+
return decoded.replace(/\.[a-z]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
74
|
+
}
|
|
75
|
+
function sectionFromUrl(url) {
|
|
76
|
+
let path;
|
|
77
|
+
try {
|
|
78
|
+
path = new URL(url).pathname;
|
|
79
|
+
} catch {
|
|
80
|
+
path = url;
|
|
81
|
+
}
|
|
82
|
+
const seg = path.split("/").filter(Boolean)[0];
|
|
83
|
+
if (!seg) return "Home";
|
|
84
|
+
let decoded;
|
|
85
|
+
try {
|
|
86
|
+
decoded = decodeURIComponent(seg);
|
|
87
|
+
} catch {
|
|
88
|
+
decoded = seg;
|
|
89
|
+
}
|
|
90
|
+
return decoded.replace(/\.[a-z]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
91
|
+
}
|
|
92
|
+
function parseSitemapXml(xml) {
|
|
93
|
+
const out = [];
|
|
94
|
+
const re = /<loc>\s*([\s\S]*?)\s*<\/loc>/gi;
|
|
95
|
+
let m;
|
|
96
|
+
while (m = re.exec(xml)) {
|
|
97
|
+
const loc = m[1].trim().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
98
|
+
if (loc) out.push({ loc });
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
60
101
|
}
|
|
61
102
|
function llmsTxtFromSitemap(entries, meta) {
|
|
103
|
+
const list = typeof entries === "string" ? parseSitemapXml(entries) : entries;
|
|
62
104
|
const bySection = /* @__PURE__ */ new Map();
|
|
63
|
-
|
|
105
|
+
const order = [];
|
|
106
|
+
const seen = /* @__PURE__ */ new Set();
|
|
107
|
+
for (const e of list) {
|
|
64
108
|
const url = e.url ?? e.loc;
|
|
65
|
-
if (!url) continue;
|
|
66
|
-
|
|
67
|
-
|
|
109
|
+
if (!url || seen.has(url)) continue;
|
|
110
|
+
seen.add(url);
|
|
111
|
+
const section = e.section ?? (meta.sectionFromPath ? sectionFromUrl(url) : meta.defaultSection ?? "Pages");
|
|
112
|
+
if (!bySection.has(section)) {
|
|
113
|
+
bySection.set(section, []);
|
|
114
|
+
order.push(section);
|
|
115
|
+
}
|
|
68
116
|
bySection.get(section).push({ title: e.title ?? titleFromUrl(url), url });
|
|
69
117
|
}
|
|
70
118
|
const doc = {
|
|
71
119
|
title: meta.title,
|
|
72
120
|
summary: meta.summary,
|
|
73
121
|
details: meta.details,
|
|
74
|
-
sections:
|
|
122
|
+
sections: order.map((title) => ({ title, links: bySection.get(title) }))
|
|
123
|
+
};
|
|
124
|
+
return llmsTxt(doc, { sort: meta.sort });
|
|
125
|
+
}
|
|
126
|
+
function llmsTxtFromRoutes(routes, meta) {
|
|
127
|
+
const bySection = /* @__PURE__ */ new Map();
|
|
128
|
+
const order = [];
|
|
129
|
+
for (const r of routes) {
|
|
130
|
+
const section = r.section ?? meta.defaultSection ?? "Docs";
|
|
131
|
+
if (!bySection.has(section)) {
|
|
132
|
+
bySection.set(section, []);
|
|
133
|
+
order.push(section);
|
|
134
|
+
}
|
|
135
|
+
bySection.get(section).push({ title: r.title, url: r.url, notes: r.notes });
|
|
136
|
+
}
|
|
137
|
+
const doc = {
|
|
138
|
+
title: meta.title,
|
|
139
|
+
summary: meta.summary,
|
|
140
|
+
details: meta.details,
|
|
141
|
+
sections: order.map((title) => ({ title, links: bySection.get(title) }))
|
|
75
142
|
};
|
|
76
|
-
return llmsTxt(doc);
|
|
143
|
+
return llmsTxt(doc, { sort: meta.sort });
|
|
77
144
|
}
|
|
78
145
|
function llmsTxtResponse(doc, init = {}) {
|
|
79
146
|
return new Response(llmsTxt(doc), {
|
|
@@ -88,6 +155,6 @@ function llmsFullTxtResponse(doc, init = {}) {
|
|
|
88
155
|
});
|
|
89
156
|
}
|
|
90
157
|
|
|
91
|
-
export { llmsFullTxt, llmsFullTxtResponse, llmsTxt, llmsTxtFromSitemap, llmsTxtResponse, parseLlmsTxt };
|
|
158
|
+
export { llmsFullTxt, llmsFullTxtResponse, llmsTxt, llmsTxtFromRoutes, llmsTxtFromSitemap, llmsTxtResponse, parseLlmsTxt };
|
|
92
159
|
//# sourceMappingURL=index.js.map
|
|
93
160
|
//# sourceMappingURL=index.js.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 * How to order the links inside each section.\n * - `\"title\"` / `\"url\"` — ascending by that field\n * - `\"title-desc\"` / `\"url-desc\"` — descending\n * - a comparator — full control\n * Sections keep their array order; only the links within a section are sorted.\n */\nexport type LinkSort =\n | \"title\"\n | \"url\"\n | \"title-desc\"\n | \"url-desc\"\n | ((a: LlmsLink, b: LlmsLink) => number);\n\n/** Options accepted by the rendering helpers. */\nexport interface LlmsTxtOptions {\n /** Order links within each section. Omit to keep the given order. */\n sort?: LinkSort;\n}\n\nfunction sortLinks(links: LlmsLink[], sort?: LinkSort): LlmsLink[] {\n if (!sort) return links;\n const copy = links.slice();\n if (typeof sort === \"function\") return copy.sort(sort);\n const [field, dir] = sort.split(\"-\") as [\"title\" | \"url\", \"desc\" | undefined];\n const factor = dir === \"desc\" ? -1 : 1;\n return copy.sort((a, b) => factor * a[field].localeCompare(b[field]));\n}\n\n/**\n * Render an `llms.txt` document.\n * @param opts optional rendering options (e.g. `{ sort: \"title\" }`).\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, opts: LlmsTxtOptions = {}): 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 sortLinks(section.links, opts.sort)) {\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/** First path segment of a URL, title-cased, for auto-sectioning (\"/docs/x\" → \"Docs\"). */\nfunction sectionFromUrl(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)[0];\n if (!seg) return \"Home\";\n let decoded: string;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n decoded = seg;\n }\n return decoded.replace(/\\.[a-z]+$/i, \"\").replace(/[-_]+/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/** Pull `<loc>` values out of a sitemap (or sitemap-index) XML string. */\nfunction parseSitemapXml(xml: string): SitemapEntryLike[] {\n const out: SitemapEntryLike[] = [];\n const re = /<loc>\\s*([\\s\\S]*?)\\s*<\\/loc>/gi;\n let m: RegExpExecArray | null;\n while ((m = re.exec(xml))) {\n const loc = m[1]!\n .trim()\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\");\n if (loc) out.push({ loc });\n }\n return out;\n}\n\n/** Meta / options for {@link llmsTxtFromSitemap}. */\nexport interface SitemapToLlmsMeta extends LlmsTxtOptions {\n title: string;\n summary?: string;\n details?: string;\n /** Fallback section name when an entry has none. Default `\"Pages\"`. */\n defaultSection?: string;\n /**\n * When an entry has no explicit `section`, derive one from the first path\n * segment (\"/docs/x\" → \"Docs\") instead of using {@link defaultSection}.\n */\n sectionFromPath?: boolean;\n}\n\n/**\n * Build an `llms.txt` from sitemap entries — accepts either an **array** of\n * `{ loc | url, title?, section? }` entries or a raw **sitemap XML string**.\n * Groups by `section` (or, with `sectionFromPath`, by the first path segment),\n * derives titles from the URL when not given, and de-duplicates repeated URLs.\n * Pairs with `@lacspace/sitemap`. The original array signature is unchanged.\n */\nexport function llmsTxtFromSitemap(\n entries: SitemapEntryLike[] | string,\n meta: SitemapToLlmsMeta,\n): string {\n const list = typeof entries === \"string\" ? parseSitemapXml(entries) : entries;\n const bySection = new Map<string, LlmsLink[]>();\n const order: string[] = [];\n const seen = new Set<string>();\n for (const e of list) {\n const url = e.url ?? e.loc;\n if (!url || seen.has(url)) continue;\n seen.add(url);\n const section =\n e.section ?? (meta.sectionFromPath ? sectionFromUrl(url) : meta.defaultSection ?? \"Pages\");\n if (!bySection.has(section)) {\n bySection.set(section, []);\n order.push(section);\n }\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: order.map((title) => ({ title, links: bySection.get(title)! })),\n };\n return llmsTxt(doc, { sort: meta.sort });\n}\n\n/* ------------------------------ from routes ------------------------------ */\n\n/** A single route/page entry for {@link llmsTxtFromRoutes}. */\nexport interface LlmsRoute {\n title: string;\n url: string;\n notes?: string;\n /** Section heading to group under. Defaults to {@link RoutesToLlmsMeta.defaultSection}. */\n section?: string;\n}\n\n/** Meta / options for {@link llmsTxtFromRoutes}. */\nexport interface RoutesToLlmsMeta extends LlmsTxtOptions {\n title: string;\n summary?: string;\n details?: string;\n /** Section name for routes with no `section`. Default `\"Docs\"`. */\n defaultSection?: string;\n}\n\n/**\n * Build an `llms.txt` from a flat list of route entries, grouping by `section`\n * (first-seen order preserved). Titles and notes are used verbatim.\n * @example\n * llmsTxtFromRoutes(\n * [\n * { title: \"Home\", url: \"https://acme.com/\", section: \"Start\" },\n * { title: \"API\", url: \"https://acme.com/api\", notes: \"reference\", section: \"Docs\" },\n * ],\n * { title: \"Acme\", summary: \"Acme docs\" },\n * );\n */\nexport function llmsTxtFromRoutes(routes: LlmsRoute[], meta: RoutesToLlmsMeta): string {\n const bySection = new Map<string, LlmsLink[]>();\n const order: string[] = [];\n for (const r of routes) {\n const section = r.section ?? meta.defaultSection ?? \"Docs\";\n if (!bySection.has(section)) {\n bySection.set(section, []);\n order.push(section);\n }\n bySection.get(section)!.push({ title: r.title, url: r.url, notes: r.notes });\n }\n const doc: LlmsDoc = {\n title: meta.title,\n summary: meta.summary,\n details: meta.details,\n sections: order.map((title) => ({ title, links: bySection.get(title)! })),\n };\n return llmsTxt(doc, { sort: meta.sort });\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.
|
|
3
|
+
"version": "1.3.0",
|
|
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",
|