meowpress 0.3.0-alpha.1 → 0.3.0-alpha.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 +8 -0
- package/dist/src/build.d.ts +7 -0
- package/dist/src/build.js +26 -6
- package/dist/src/pages.d.ts +12 -0
- package/dist/src/pages.js +14 -0
- package/dist/src/site.d.ts +6 -1
- package/dist/src/site.js +81 -25
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -50,6 +50,14 @@ export default meowSite({
|
|
|
50
50
|
|
|
51
51
|
- `pages` is a provider of complete HTML documents. A path that resolves above the site root is
|
|
52
52
|
refused rather than clamped.
|
|
53
|
+
- A page's optional `markdown` is the same page for agents. It is published next to the route
|
|
54
|
+
(`components/button.md` for `/components/button/`, `index.md` for the root), and `llms.txt`,
|
|
55
|
+
`llm.txt` and `agents.md` link it instead of the HTML. `llms.txt` follows the
|
|
56
|
+
[llms.txt](https://llmstxt.org/) shape: the site summary, then one section of links per page
|
|
57
|
+
`group` with each page's `description`. `llms-full.txt` holds every Markdown copy in one file.
|
|
58
|
+
- `agentGuide` is Markdown that opens `llms.txt` and `llms-full.txt` after the summary: what an
|
|
59
|
+
agent must know before following any link, such as how to install and set up the documented
|
|
60
|
+
packages.
|
|
53
61
|
- `mdxComponents` adds named components to the MDX content pages; the built-in `Callout` stays
|
|
54
62
|
available.
|
|
55
63
|
- `themeStylesheet` names the theme stylesheet the build writes, so a consumer page can link it. It
|
package/dist/src/build.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type SiteFont } from "./fonts.js";
|
|
|
3
3
|
import { type SiteIdentity } from "./identity.js";
|
|
4
4
|
import { type ContentSourceInput, type MdxComponent } from "./content.js";
|
|
5
5
|
import { type SitePageProvider } from "./pages.js";
|
|
6
|
+
export { sitePageMarkdownFile } from "./pages.js";
|
|
6
7
|
export type { SitePage, SitePageContext, SitePageProvider } from "./pages.js";
|
|
7
8
|
export type { ContentItem } from "./content.js";
|
|
8
9
|
export type { SiteFont } from "./fonts.js";
|
|
@@ -22,6 +23,11 @@ export interface SiteConfig {
|
|
|
22
23
|
site?: Partial<SiteIdentity>;
|
|
23
24
|
/** Consumer-supplied pages; the site writes and indexes them next to its own. */
|
|
24
25
|
pages?: SitePageProvider;
|
|
26
|
+
/**
|
|
27
|
+
* Markdown that opens `llms.txt` and `llms-full.txt` after the site summary: what an agent must
|
|
28
|
+
* know before following any link, such as how to install and set up what the site documents.
|
|
29
|
+
*/
|
|
30
|
+
agentGuide?: string;
|
|
25
31
|
/** Extra components available to MDX content pages. */
|
|
26
32
|
mdxComponents?: Record<string, MdxComponent>;
|
|
27
33
|
/** Stylesheet file name for the theme; defaults to "blog-theme.css". */
|
|
@@ -40,6 +46,7 @@ export interface ResolvedSiteConfig {
|
|
|
40
46
|
projectRootDirectory?: string;
|
|
41
47
|
site: SiteIdentity;
|
|
42
48
|
pages?: SitePageProvider;
|
|
49
|
+
agentGuide?: string;
|
|
43
50
|
mdxComponents: Record<string, MdxComponent>;
|
|
44
51
|
themeStylesheet: string;
|
|
45
52
|
fonts: readonly SiteFont[];
|
package/dist/src/build.js
CHANGED
|
@@ -5,9 +5,10 @@ import { DEFAULT_SITE } from "./identity.js";
|
|
|
5
5
|
import { mkdir, readFile, rm, writeFile, cp } from "node:fs/promises";
|
|
6
6
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
7
7
|
import { collectContentAssets, isDraftAsset, loadSection, normalizeContentSources, } from "./content.js";
|
|
8
|
-
import { aboutPage, agentMarkdown, DEFAULT_THEME_STYLESHEET, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
8
|
+
import { aboutPage, agentMarkdown, DEFAULT_THEME_STYLESHEET, homePage, listPage, llmFullText, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
9
9
|
import { loadSkills } from "./skills.js";
|
|
10
|
-
import { sitePageFile } from "./pages.js";
|
|
10
|
+
import { sitePageFile, sitePageMarkdownFile } from "./pages.js";
|
|
11
|
+
export { sitePageMarkdownFile } from "./pages.js";
|
|
11
12
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
12
13
|
export function meowSite(config) {
|
|
13
14
|
return {
|
|
@@ -20,6 +21,7 @@ export function meowSite(config) {
|
|
|
20
21
|
: undefined,
|
|
21
22
|
site: { ...DEFAULT_SITE, ...config.site },
|
|
22
23
|
pages: config.pages,
|
|
24
|
+
agentGuide: config.agentGuide,
|
|
23
25
|
mdxComponents: config.mdxComponents ?? {},
|
|
24
26
|
themeStylesheet: config.themeStylesheet ?? DEFAULT_THEME_STYLESHEET,
|
|
25
27
|
fonts: config.fonts ?? (config.theme === undefined || config.theme === blogTheme ? blogFonts : []),
|
|
@@ -71,8 +73,8 @@ async function buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, o
|
|
|
71
73
|
await writeFile(output.path, removeLegacyIsFallbacks(await readFile(output.path, "utf8")));
|
|
72
74
|
}
|
|
73
75
|
/** Consumer pages are written inside the generated directory and never above it. */
|
|
74
|
-
function consumerPageFile(generatedDirectory, path) {
|
|
75
|
-
const file =
|
|
76
|
+
function consumerPageFile(generatedDirectory, path, fileFor = sitePageFile) {
|
|
77
|
+
const file = fileFor(path);
|
|
76
78
|
const target = resolve(generatedDirectory, file);
|
|
77
79
|
const inside = relative(generatedDirectory, target);
|
|
78
80
|
if (!inside || inside.startsWith("..") || isAbsolute(inside)) {
|
|
@@ -87,6 +89,18 @@ async function writeConsumerPages(generatedDirectory, pages) {
|
|
|
87
89
|
}
|
|
88
90
|
return entrypoints;
|
|
89
91
|
}
|
|
92
|
+
/** Files the build writes itself; a page's Markdown copy may not take their place. */
|
|
93
|
+
const RESERVED_MARKDOWN = new Set(["agents.md"]);
|
|
94
|
+
/** Each page's Markdown copy, written straight into the output next to its route. */
|
|
95
|
+
async function writeConsumerMarkdown(outputDirectory, pages) {
|
|
96
|
+
for (const page of pages) {
|
|
97
|
+
if (page.markdown === undefined)
|
|
98
|
+
continue;
|
|
99
|
+
if (RESERVED_MARKDOWN.has(sitePageMarkdownFile(page.path)))
|
|
100
|
+
throw new Error(`Site page Markdown would replace the agent index: ${page.path}`);
|
|
101
|
+
await writePage(outputDirectory, consumerPageFile(outputDirectory, page.path, sitePageMarkdownFile), page.markdown);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
90
104
|
async function writeContentPages(generatedDirectory, posts, thoughts, theme, site, stylesheet) {
|
|
91
105
|
const entrypoints = [];
|
|
92
106
|
for (const post of posts) {
|
|
@@ -148,9 +162,15 @@ export async function buildSite(input) {
|
|
|
148
162
|
await stageAssets(generatedDirectory, config.content);
|
|
149
163
|
await buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, config.outputDirectory);
|
|
150
164
|
await copyPostAssets(config.outputDirectory, config.content);
|
|
165
|
+
if (pages)
|
|
166
|
+
await writeConsumerMarkdown(config.outputDirectory, pages);
|
|
167
|
+
const full = pages ? llmFullText(config.site, pages, config.agentGuide) : undefined;
|
|
151
168
|
await Promise.all([
|
|
152
|
-
writeFile(join(config.outputDirectory, "llm.txt"), llmText(posts, thoughts, config.site, pages), "utf8"),
|
|
153
|
-
writeFile(join(config.outputDirectory, "llms.txt"), llmText(posts, thoughts, config.site, pages), "utf8"),
|
|
169
|
+
writeFile(join(config.outputDirectory, "llm.txt"), llmText(posts, thoughts, config.site, pages, config.agentGuide), "utf8"),
|
|
170
|
+
writeFile(join(config.outputDirectory, "llms.txt"), llmText(posts, thoughts, config.site, pages, config.agentGuide), "utf8"),
|
|
171
|
+
...(full === undefined
|
|
172
|
+
? []
|
|
173
|
+
: [writeFile(join(config.outputDirectory, "llms-full.txt"), full, "utf8")]),
|
|
154
174
|
writeFile(join(config.outputDirectory, "agents.md"), agentMarkdown(posts, thoughts, config.site, pages), "utf8"),
|
|
155
175
|
]);
|
|
156
176
|
await rm(generatedDirectory, { recursive: true, force: true });
|
package/dist/src/pages.d.ts
CHANGED
|
@@ -8,6 +8,11 @@ export interface SitePage {
|
|
|
8
8
|
title: string;
|
|
9
9
|
/** A complete HTML document for the route. */
|
|
10
10
|
html: string;
|
|
11
|
+
/**
|
|
12
|
+
* The same page as Markdown for agents. When present it is published next to the route (see
|
|
13
|
+
* `sitePageMarkdownFile`), and the agent-facing indexes link it instead of the HTML document.
|
|
14
|
+
*/
|
|
15
|
+
markdown?: string;
|
|
11
16
|
description?: string;
|
|
12
17
|
/** Grouping used by the site index and the agent-facing indexes. */
|
|
13
18
|
group?: string;
|
|
@@ -26,3 +31,10 @@ export type SitePageProvider = (context: SitePageContext) => readonly SitePage[]
|
|
|
26
31
|
export declare function sitePageUrl(path: string): string;
|
|
27
32
|
/** The output file a page is written to, relative to the site root. */
|
|
28
33
|
export declare function sitePageFile(path: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* The Markdown copy of a page, relative to the site root: "index.md" for the root and
|
|
36
|
+
* "components/button.md" for /components/button/, the convention llms.txt readers expect.
|
|
37
|
+
*/
|
|
38
|
+
export declare function sitePageMarkdownFile(path: string): string;
|
|
39
|
+
/** Where an agent reads a page: its Markdown copy when it has one, its HTML route otherwise. */
|
|
40
|
+
export declare function sitePageAgentUrl(page: SitePage): string;
|
package/dist/src/pages.js
CHANGED
|
@@ -8,3 +8,17 @@ export function sitePageFile(path) {
|
|
|
8
8
|
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
9
9
|
return normalized ? `${normalized}/index.html` : "index.html";
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* The Markdown copy of a page, relative to the site root: "index.md" for the root and
|
|
13
|
+
* "components/button.md" for /components/button/, the convention llms.txt readers expect.
|
|
14
|
+
*/
|
|
15
|
+
export function sitePageMarkdownFile(path) {
|
|
16
|
+
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
17
|
+
return normalized ? `${normalized}.md` : "index.md";
|
|
18
|
+
}
|
|
19
|
+
/** Where an agent reads a page: its Markdown copy when it has one, its HTML route otherwise. */
|
|
20
|
+
export function sitePageAgentUrl(page) {
|
|
21
|
+
return page.markdown === undefined
|
|
22
|
+
? sitePageUrl(page.path)
|
|
23
|
+
: `/${sitePageMarkdownFile(page.path)}`;
|
|
24
|
+
}
|
package/dist/src/site.d.ts
CHANGED
|
@@ -13,4 +13,9 @@ export declare function aboutPage(theme?: ThemeDefinition, site?: SiteIdentity,
|
|
|
13
13
|
export declare function skillsIndexPage(skills: Skill[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
14
14
|
export declare function skillPage(skill: Skill, theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
15
15
|
export declare function agentMarkdown(posts: ContentItem[], thoughts: ContentItem[], site?: SiteIdentity, pages?: readonly SitePage[]): string;
|
|
16
|
-
export declare function llmText(posts: ContentItem[], thoughts: ContentItem[], site?: SiteIdentity, pages?: readonly SitePage[]): string;
|
|
16
|
+
export declare function llmText(posts: ContentItem[], thoughts: ContentItem[], site?: SiteIdentity, pages?: readonly SitePage[], agentGuide?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Every page's Markdown in one file, after the same summary and guide as llms.txt. Pages without
|
|
19
|
+
* a Markdown copy are left out; llms.txt still links their HTML.
|
|
20
|
+
*/
|
|
21
|
+
export declare function llmFullText(site: SiteIdentity, pages: readonly SitePage[], agentGuide?: string): string | undefined;
|
package/dist/src/site.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Fragment, h } from "meowjsx/runtime";
|
|
|
4
4
|
import { renderToHtml } from "meowjsx/web";
|
|
5
5
|
import { withoutLeadingHeading } from "./content.js";
|
|
6
6
|
import { DEFAULT_SITE } from "./identity.js";
|
|
7
|
-
import {
|
|
7
|
+
import { sitePageAgentUrl } from "./pages.js";
|
|
8
8
|
import { layoutSkillGraph } from "./skill-graph.js";
|
|
9
9
|
/** The name of the theme stylesheet the package writes by default. */
|
|
10
10
|
export const DEFAULT_THEME_STYLESHEET = "blog-theme.css";
|
|
@@ -675,7 +675,7 @@ export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE, stylesh
|
|
|
675
675
|
}
|
|
676
676
|
export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE, pages) {
|
|
677
677
|
const siteMap = pages
|
|
678
|
-
? pages.map((page) => `- [${page.title}](${
|
|
678
|
+
? pages.map((page) => `- [${page.title}](${sitePageAgentUrl(page)})`)
|
|
679
679
|
: [
|
|
680
680
|
"- [Home](/)",
|
|
681
681
|
"- [Posts](/posts/)",
|
|
@@ -720,38 +720,94 @@ export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE, pages) {
|
|
|
720
720
|
];
|
|
721
721
|
return lines.join("\n");
|
|
722
722
|
}
|
|
723
|
-
export function llmText(posts, thoughts, site = DEFAULT_SITE, pages) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
"- Posts: /posts/",
|
|
729
|
-
"- Thoughts: /thoughts/",
|
|
730
|
-
"- Skills: /skills/",
|
|
731
|
-
"- About: /about/",
|
|
732
|
-
];
|
|
723
|
+
export function llmText(posts, thoughts, site = DEFAULT_SITE, pages, agentGuide) {
|
|
724
|
+
// A consumer that owns the site map owns the routes of its content too, so its index follows
|
|
725
|
+
// the llms.txt shape: the summary, the guide, then one section of links per page group.
|
|
726
|
+
if (pages)
|
|
727
|
+
return consumerLlmText(site, pages, agentGuide);
|
|
733
728
|
return [
|
|
734
729
|
`# ${site.name}`,
|
|
735
730
|
`> ${site.description}`,
|
|
736
731
|
"",
|
|
737
732
|
"## Pages",
|
|
738
|
-
|
|
733
|
+
"- Home: /",
|
|
734
|
+
"- Posts: /posts/",
|
|
735
|
+
"- Thoughts: /thoughts/",
|
|
736
|
+
"- Skills: /skills/",
|
|
737
|
+
"- About: /about/",
|
|
739
738
|
"- Agent Markdown: /agents.md",
|
|
740
739
|
"",
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
...posts.map((post) => `- ${post.title}: ${itemUrl(post)}`),
|
|
748
|
-
"",
|
|
749
|
-
"## Thoughts",
|
|
750
|
-
...thoughts.map((thought) => `- ${thought.title}: ${itemUrl(thought)}`),
|
|
751
|
-
"",
|
|
752
|
-
]),
|
|
740
|
+
"## Posts",
|
|
741
|
+
...posts.map((post) => `- ${post.title}: ${itemUrl(post)}`),
|
|
742
|
+
"",
|
|
743
|
+
"## Thoughts",
|
|
744
|
+
...thoughts.map((thought) => `- ${thought.title}: ${itemUrl(thought)}`),
|
|
745
|
+
"",
|
|
753
746
|
"## Rules",
|
|
754
747
|
"- Only the public build output is considered presentable content.",
|
|
755
748
|
"- Drafts whose names start with _ under posts are not published or rendered.",
|
|
756
749
|
].join("\n");
|
|
757
750
|
}
|
|
751
|
+
/** The pages by group, in the order each group first appears. */
|
|
752
|
+
function pageGroups(pages) {
|
|
753
|
+
const groups = new Map();
|
|
754
|
+
for (const page of pages) {
|
|
755
|
+
const group = page.group ?? "Pages";
|
|
756
|
+
const members = groups.get(group);
|
|
757
|
+
if (members)
|
|
758
|
+
members.push(page);
|
|
759
|
+
else
|
|
760
|
+
groups.set(group, [page]);
|
|
761
|
+
}
|
|
762
|
+
return [...groups];
|
|
763
|
+
}
|
|
764
|
+
/** One llms.txt link: the page's Markdown when it has one, and its description after a colon. */
|
|
765
|
+
function llmLink(page) {
|
|
766
|
+
const description = page.description?.replace(/\s+/g, " ").trim();
|
|
767
|
+
return `- [${page.title}](${sitePageAgentUrl(page)})${description ? `: ${description}` : ""}`;
|
|
768
|
+
}
|
|
769
|
+
function consumerLlmText(site, pages, agentGuide) {
|
|
770
|
+
const full = pages.some((page) => page.markdown !== undefined);
|
|
771
|
+
return [
|
|
772
|
+
`# ${site.name}`,
|
|
773
|
+
"",
|
|
774
|
+
`> ${site.description}`,
|
|
775
|
+
"",
|
|
776
|
+
...(agentGuide ? [agentGuide.trim(), ""] : []),
|
|
777
|
+
...pageGroups(pages).flatMap(([group, members]) => [
|
|
778
|
+
`## ${group}`,
|
|
779
|
+
"",
|
|
780
|
+
...members.map(llmLink),
|
|
781
|
+
"",
|
|
782
|
+
]),
|
|
783
|
+
"## Optional",
|
|
784
|
+
"",
|
|
785
|
+
...(full
|
|
786
|
+
? [
|
|
787
|
+
"- [Full text](/llms-full.txt): every page linked here as Markdown, in one file; large, so fetch single pages first",
|
|
788
|
+
]
|
|
789
|
+
: []),
|
|
790
|
+
"- [Agent Markdown](/agents.md): the site map",
|
|
791
|
+
"",
|
|
792
|
+
].join("\n");
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Every page's Markdown in one file, after the same summary and guide as llms.txt. Pages without
|
|
796
|
+
* a Markdown copy are left out; llms.txt still links their HTML.
|
|
797
|
+
*/
|
|
798
|
+
export function llmFullText(site, pages, agentGuide) {
|
|
799
|
+
const documents = pages.flatMap((page) => page.markdown === undefined
|
|
800
|
+
? []
|
|
801
|
+
: [`<!-- Source: ${sitePageAgentUrl(page)} -->\n\n${page.markdown.trim()}`]);
|
|
802
|
+
if (!documents.length)
|
|
803
|
+
return undefined;
|
|
804
|
+
return [
|
|
805
|
+
`# ${site.name}`,
|
|
806
|
+
"",
|
|
807
|
+
`> ${site.description}`,
|
|
808
|
+
"",
|
|
809
|
+
...(agentGuide ? [agentGuide.trim(), ""] : []),
|
|
810
|
+
documents.join("\n\n---\n\n"),
|
|
811
|
+
"",
|
|
812
|
+
].join("\n");
|
|
813
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meowpress",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"meowjsx": "^0.3.0-alpha.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"meowjsx": "0.3.0-alpha.
|
|
45
|
-
"meowcss": "0.3.0-alpha.
|
|
44
|
+
"meowjsx": "0.3.0-alpha.1",
|
|
45
|
+
"meowcss": "0.3.0-alpha.2"
|
|
46
46
|
}
|
|
47
47
|
}
|