@marlinjai/clearify 2.0.0 → 2.1.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/dist/node/{chunk-CK2SLBAG.js → chunk-GZE6QLJT.js} +1 -1
- package/dist/node/{chunk-BCEGDIR6.js → chunk-PZA4UAFG.js} +42 -2
- package/dist/node/{chunk-RW4IKHY4.js → chunk-Q5FUAK6J.js} +1 -1
- package/dist/node/chunk-TVDWHKQF.js +134 -0
- package/dist/node/{chunk-PGTNVDNF.js → chunk-ZWF6VCSQ.js} +7 -1
- package/dist/node/core/config.js +1 -1
- package/dist/node/core/llms-txt.d.ts +54 -0
- package/dist/node/core/llms-txt.js +10 -0
- package/dist/node/node/build.js +4 -3
- package/dist/node/node/check.js +2 -2
- package/dist/node/node/index.js +5 -4
- package/dist/node/types/index.d.ts +6 -0
- package/dist/node/vite-plugin/index.js +2 -2
- package/package.json +1 -1
|
@@ -6,14 +6,18 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
mermaidContentHash
|
|
8
8
|
} from "./chunk-SCZZB7OE.js";
|
|
9
|
+
import {
|
|
10
|
+
generateLlmsFullTxt,
|
|
11
|
+
generateLlmsTxt
|
|
12
|
+
} from "./chunk-TVDWHKQF.js";
|
|
9
13
|
import {
|
|
10
14
|
clearifyPlugin
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-Q5FUAK6J.js";
|
|
12
16
|
import {
|
|
13
17
|
loadUserConfig,
|
|
14
18
|
resolveConfig,
|
|
15
19
|
resolveSections
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-ZWF6VCSQ.js";
|
|
17
21
|
import {
|
|
18
22
|
buildSectionData
|
|
19
23
|
} from "./chunk-CTL4YL7C.js";
|
|
@@ -72,6 +76,29 @@ function generateSitemap(routes, siteUrl) {
|
|
|
72
76
|
${urls.join("\n")}
|
|
73
77
|
</urlset>`;
|
|
74
78
|
}
|
|
79
|
+
function formatBytes(bytes) {
|
|
80
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
81
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
82
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
83
|
+
}
|
|
84
|
+
function collectLlmsDocs(sections) {
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const sd of sections) {
|
|
87
|
+
for (const doc of sd.docs) {
|
|
88
|
+
const title = doc.frontmatter.title ?? "Untitled";
|
|
89
|
+
const optional = (doc.frontmatter.order ?? 0) >= 9e3;
|
|
90
|
+
const markdown = existsSync(doc.filePath) ? readFileSync(doc.filePath, "utf-8") : "";
|
|
91
|
+
out.push({
|
|
92
|
+
title,
|
|
93
|
+
routePath: doc.routePath,
|
|
94
|
+
sectionLabel: sd.section.label,
|
|
95
|
+
markdown,
|
|
96
|
+
optional
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
75
102
|
function generateRobotsTxt(siteUrl) {
|
|
76
103
|
const lines = ["User-agent: *", "Allow: /"];
|
|
77
104
|
if (siteUrl) {
|
|
@@ -312,6 +339,19 @@ async function buildSite() {
|
|
|
312
339
|
const sitemap = generateSitemap(sitemapRoutes, config.siteUrl);
|
|
313
340
|
writeFileSync(resolve(outDir, "sitemap.xml"), sitemap);
|
|
314
341
|
console.log(" Generated sitemap.xml");
|
|
342
|
+
if (config.generateLlmsTxt !== false) {
|
|
343
|
+
const llmsDocs = collectLlmsDocs(sectionDataList);
|
|
344
|
+
const llmsTxt = generateLlmsTxt(config, llmsDocs);
|
|
345
|
+
const llmsFullTxt = generateLlmsFullTxt(config, llmsDocs);
|
|
346
|
+
const llmsTxtPath = resolve(outDir, "llms.txt");
|
|
347
|
+
const llmsFullTxtPath = resolve(outDir, "llms-full.txt");
|
|
348
|
+
writeFileSync(llmsTxtPath, llmsTxt);
|
|
349
|
+
writeFileSync(llmsFullTxtPath, llmsFullTxt);
|
|
350
|
+
const llmsTxtSize = Buffer.byteLength(llmsTxt, "utf-8");
|
|
351
|
+
const llmsFullSize = Buffer.byteLength(llmsFullTxt, "utf-8");
|
|
352
|
+
console.log(` Generated ${llmsTxtPath} (${formatBytes(llmsTxtSize)})`);
|
|
353
|
+
console.log(` Generated ${llmsFullTxtPath} (${formatBytes(llmsFullSize)})`);
|
|
354
|
+
}
|
|
315
355
|
rmSync(ssrOutDir, { recursive: true, force: true });
|
|
316
356
|
console.log(" Cleaned up SSR build artifacts.");
|
|
317
357
|
if (mermaidStrategy === "build") {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// src/core/llms-txt.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
function normalizeBase(siteUrl) {
|
|
5
|
+
if (!siteUrl) return "";
|
|
6
|
+
return siteUrl.replace(/\/$/, "");
|
|
7
|
+
}
|
|
8
|
+
function toAbsoluteUrl(base, routePath) {
|
|
9
|
+
if (!base) return routePath;
|
|
10
|
+
if (!routePath.startsWith("/")) return `${base}/${routePath}`;
|
|
11
|
+
return `${base}${routePath}`;
|
|
12
|
+
}
|
|
13
|
+
function stripFrontmatterAndH1(markdown) {
|
|
14
|
+
const { content } = matter(markdown);
|
|
15
|
+
return content.replace(/^\s*#\s+[^\n]*\n+/, "").trimStart();
|
|
16
|
+
}
|
|
17
|
+
function firstParagraphSummary(markdown) {
|
|
18
|
+
const parsed = matter(markdown);
|
|
19
|
+
const fromFrontmatter = typeof parsed.data.summary === "string" && parsed.data.summary.trim() || typeof parsed.data.description === "string" && parsed.data.description.trim() || "";
|
|
20
|
+
const body = parsed.content;
|
|
21
|
+
const lines = body.split(/\r?\n/);
|
|
22
|
+
let inFence = false;
|
|
23
|
+
const paragraph = [];
|
|
24
|
+
for (const rawLine of lines) {
|
|
25
|
+
const line = rawLine.trimEnd();
|
|
26
|
+
if (/^```/.test(line)) {
|
|
27
|
+
inFence = !inFence;
|
|
28
|
+
if (paragraph.length > 0) break;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (inFence) continue;
|
|
32
|
+
if (!line.trim()) {
|
|
33
|
+
if (paragraph.length > 0) break;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (/^(#{1,6}\s|>\s|[-*+]\s|\d+\.\s|\|.*\|\s*$|<)/.test(line)) {
|
|
37
|
+
if (paragraph.length > 0) break;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
paragraph.push(line.trim());
|
|
41
|
+
}
|
|
42
|
+
const text = paragraph.join(" ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/([*_]{1,3})([^*_]+)\1/g, "$2").replace(/\s+/g, " ").trim();
|
|
43
|
+
return text || fromFrontmatter;
|
|
44
|
+
}
|
|
45
|
+
function uniqueOrdered(items) {
|
|
46
|
+
const seen = /* @__PURE__ */ new Set();
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const item of items) {
|
|
49
|
+
if (seen.has(item)) continue;
|
|
50
|
+
seen.add(item);
|
|
51
|
+
out.push(item);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function buildHeader(config) {
|
|
56
|
+
const name = config.name.trim();
|
|
57
|
+
const tagline = config.hubProject?.description && config.hubProject.description.trim() || `Documentation for ${name}.`;
|
|
58
|
+
return `# ${name}
|
|
59
|
+
|
|
60
|
+
> ${tagline}
|
|
61
|
+
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
function generateLlmsTxt(config, docs) {
|
|
65
|
+
const base = normalizeBase(config.siteUrl);
|
|
66
|
+
let out = buildHeader(config);
|
|
67
|
+
const mainDocs = docs.filter((d) => !d.optional);
|
|
68
|
+
const optionalDocs = docs.filter((d) => d.optional);
|
|
69
|
+
const sectionLabels = uniqueOrdered(mainDocs.map((d) => d.sectionLabel));
|
|
70
|
+
for (const label of sectionLabels) {
|
|
71
|
+
const sectionDocs = mainDocs.filter((d) => d.sectionLabel === label);
|
|
72
|
+
if (sectionDocs.length === 0) continue;
|
|
73
|
+
out += `## ${label}
|
|
74
|
+
|
|
75
|
+
`;
|
|
76
|
+
for (const doc of sectionDocs) {
|
|
77
|
+
const url = toAbsoluteUrl(base, doc.routePath);
|
|
78
|
+
const summary = firstParagraphSummary(doc.markdown);
|
|
79
|
+
if (summary) {
|
|
80
|
+
out += `- [${doc.title}](${url}): ${summary}
|
|
81
|
+
`;
|
|
82
|
+
} else {
|
|
83
|
+
out += `- [${doc.title}](${url})
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
out += "\n";
|
|
88
|
+
}
|
|
89
|
+
if (optionalDocs.length > 0) {
|
|
90
|
+
out += `## Optional
|
|
91
|
+
|
|
92
|
+
`;
|
|
93
|
+
for (const doc of optionalDocs) {
|
|
94
|
+
const url = toAbsoluteUrl(base, doc.routePath);
|
|
95
|
+
const summary = firstParagraphSummary(doc.markdown);
|
|
96
|
+
if (summary) {
|
|
97
|
+
out += `- [${doc.title}](${url}): ${summary}
|
|
98
|
+
`;
|
|
99
|
+
} else {
|
|
100
|
+
out += `- [${doc.title}](${url})
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
out += "\n";
|
|
105
|
+
}
|
|
106
|
+
return out.trimEnd() + "\n";
|
|
107
|
+
}
|
|
108
|
+
function generateLlmsFullTxt(config, docs) {
|
|
109
|
+
const base = normalizeBase(config.siteUrl);
|
|
110
|
+
let out = buildHeader(config);
|
|
111
|
+
for (const doc of docs) {
|
|
112
|
+
const url = toAbsoluteUrl(base, doc.routePath);
|
|
113
|
+
const body = stripFrontmatterAndH1(doc.markdown);
|
|
114
|
+
out += `## ${doc.title}
|
|
115
|
+
|
|
116
|
+
`;
|
|
117
|
+
out += `Source: ${url}
|
|
118
|
+
|
|
119
|
+
`;
|
|
120
|
+
if (body) {
|
|
121
|
+
out += body.trimEnd() + "\n\n";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out.trimEnd() + "\n";
|
|
125
|
+
}
|
|
126
|
+
function readMarkdownFile(filePath) {
|
|
127
|
+
return readFileSync(filePath, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
generateLlmsTxt,
|
|
132
|
+
generateLlmsFullTxt,
|
|
133
|
+
readMarkdownFile
|
|
134
|
+
};
|
|
@@ -117,7 +117,8 @@ var ClearifyConfigSchema = z.object({
|
|
|
117
117
|
hubProject: HubProjectPartialSchema,
|
|
118
118
|
customCss: z.string().optional(),
|
|
119
119
|
headTags: z.array(z.string()).optional(),
|
|
120
|
-
includeReadme: z.boolean().default(false)
|
|
120
|
+
includeReadme: z.boolean().default(false),
|
|
121
|
+
generateLlmsTxt: z.boolean().default(true)
|
|
121
122
|
});
|
|
122
123
|
var ClearifyDataSchema = z.object({
|
|
123
124
|
name: z.string().optional(),
|
|
@@ -265,6 +266,11 @@ function resolveConfig(userConfig = {}, root) {
|
|
|
265
266
|
if (!userConfig.name && root) {
|
|
266
267
|
parsed.name = detectProjectName(root);
|
|
267
268
|
}
|
|
269
|
+
if (parsed.generateLlmsTxt && !parsed.siteUrl) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
"clearify config: `generateLlmsTxt` is enabled (default) but `siteUrl` is not set. Set `siteUrl` to the production URL of your docs site, or set `generateLlmsTxt: false` to disable."
|
|
272
|
+
);
|
|
273
|
+
}
|
|
268
274
|
return parsed;
|
|
269
275
|
}
|
|
270
276
|
function slugify(str) {
|
package/dist/node/core/config.js
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ClearifyConfig } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A doc entry as consumed by the llms.txt generators. Produced from the
|
|
5
|
+
* scanned section data by callers (e.g. the build pipeline) so these
|
|
6
|
+
* functions stay pure string builders.
|
|
7
|
+
*/
|
|
8
|
+
interface LlmsDocEntry {
|
|
9
|
+
/** Page title (from frontmatter or derived). */
|
|
10
|
+
title: string;
|
|
11
|
+
/** Route path on the site, e.g. `/getting-started`. */
|
|
12
|
+
routePath: string;
|
|
13
|
+
/** Section label the page belongs to, e.g. `Documentation`. */
|
|
14
|
+
sectionLabel: string;
|
|
15
|
+
/** Raw markdown source (frontmatter optional, handled by the generator). */
|
|
16
|
+
markdown: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional flag. When true the page goes under the `## Optional` bucket at
|
|
19
|
+
* the end of llms.txt rather than the main section list. Use for auxiliary
|
|
20
|
+
* content like a changelog or roadmap.
|
|
21
|
+
*/
|
|
22
|
+
optional?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate the `llms.txt` index file content.
|
|
26
|
+
*
|
|
27
|
+
* Format:
|
|
28
|
+
* # {name}
|
|
29
|
+
*
|
|
30
|
+
* > {tagline}
|
|
31
|
+
*
|
|
32
|
+
* ## {Section label}
|
|
33
|
+
*
|
|
34
|
+
* - [Title](https://site/path): first-paragraph summary
|
|
35
|
+
* ...
|
|
36
|
+
*
|
|
37
|
+
* ## Optional
|
|
38
|
+
* - [Title](https://site/path): summary
|
|
39
|
+
*/
|
|
40
|
+
declare function generateLlmsTxt(config: ClearifyConfig, docs: LlmsDocEntry[]): string;
|
|
41
|
+
/**
|
|
42
|
+
* Generate the `llms-full.txt` bundle file content. Concatenates the entire
|
|
43
|
+
* markdown body of every doc under an H2 with a `Source:` line, so an LLM
|
|
44
|
+
* with no browsing can ingest the whole site from one file.
|
|
45
|
+
*/
|
|
46
|
+
declare function generateLlmsFullTxt(config: ClearifyConfig, docs: LlmsDocEntry[]): string;
|
|
47
|
+
/**
|
|
48
|
+
* Read a markdown file from disk. Returned as-is, including frontmatter.
|
|
49
|
+
* Exists here so callers in the build pipeline stay thin and the unit tests
|
|
50
|
+
* can stub in-memory markdown without touching the filesystem.
|
|
51
|
+
*/
|
|
52
|
+
declare function readMarkdownFile(filePath: string): string;
|
|
53
|
+
|
|
54
|
+
export { type LlmsDocEntry, generateLlmsFullTxt, generateLlmsTxt, readMarkdownFile };
|
package/dist/node/node/build.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-PZA4UAFG.js";
|
|
4
4
|
import "../chunk-PRTER35L.js";
|
|
5
5
|
import "../chunk-SCZZB7OE.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-TVDWHKQF.js";
|
|
7
|
+
import "../chunk-Q5FUAK6J.js";
|
|
7
8
|
import "../chunk-B2Q23JW3.js";
|
|
8
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-ZWF6VCSQ.js";
|
|
9
10
|
import "../chunk-AJBCHKBP.js";
|
|
10
11
|
import "../chunk-V7LLYIRO.js";
|
|
11
12
|
import "../chunk-CTL4YL7C.js";
|
package/dist/node/node/check.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkFrontmatter,
|
|
3
3
|
checkLinks
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-GZE6QLJT.js";
|
|
5
|
+
import "../chunk-ZWF6VCSQ.js";
|
|
6
6
|
import "../chunk-AJBCHKBP.js";
|
|
7
7
|
import "../chunk-V7LLYIRO.js";
|
|
8
8
|
import "../chunk-CTL4YL7C.js";
|
package/dist/node/node/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-PZA4UAFG.js";
|
|
4
4
|
import {
|
|
5
5
|
remarkMermaidToComponent
|
|
6
6
|
} from "../chunk-PRTER35L.js";
|
|
7
7
|
import "../chunk-SCZZB7OE.js";
|
|
8
|
+
import "../chunk-TVDWHKQF.js";
|
|
8
9
|
import {
|
|
9
10
|
clearifyPlugin
|
|
10
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-Q5FUAK6J.js";
|
|
11
12
|
import "../chunk-B2Q23JW3.js";
|
|
12
13
|
import {
|
|
13
14
|
init
|
|
@@ -15,11 +16,11 @@ import {
|
|
|
15
16
|
import {
|
|
16
17
|
checkFrontmatter,
|
|
17
18
|
checkLinks
|
|
18
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-GZE6QLJT.js";
|
|
19
20
|
import {
|
|
20
21
|
loadUserConfig,
|
|
21
22
|
resolveConfig
|
|
22
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-ZWF6VCSQ.js";
|
|
23
24
|
import "../chunk-AJBCHKBP.js";
|
|
24
25
|
import "../chunk-V7LLYIRO.js";
|
|
25
26
|
import "../chunk-CTL4YL7C.js";
|
|
@@ -62,6 +62,12 @@ interface ClearifyConfig {
|
|
|
62
62
|
customCss?: string;
|
|
63
63
|
headTags?: string[];
|
|
64
64
|
includeReadme?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* When true (default), `clearify build` writes `llms.txt` and
|
|
67
|
+
* `llms-full.txt` into the output directory for AI agents and crawlers.
|
|
68
|
+
* Requires `siteUrl` to be set. Set to `false` to skip generation.
|
|
69
|
+
*/
|
|
70
|
+
generateLlmsTxt?: boolean;
|
|
65
71
|
}
|
|
66
72
|
interface NavigationItem {
|
|
67
73
|
label: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clearifyPlugin
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-Q5FUAK6J.js";
|
|
4
4
|
import "../chunk-B2Q23JW3.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-ZWF6VCSQ.js";
|
|
6
6
|
import "../chunk-AJBCHKBP.js";
|
|
7
7
|
import "../chunk-V7LLYIRO.js";
|
|
8
8
|
import "../chunk-CTL4YL7C.js";
|