meowpress 0.3.0-alpha.0 → 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 +21 -5
- package/dist/src/build.d.ts +15 -0
- package/dist/src/build.js +41 -15
- package/dist/src/fonts.d.ts +21 -0
- package/dist/src/fonts.js +56 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +1 -1
- 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/dist/src/theme.d.ts +8 -6
- package/dist/src/theme.js +5 -6
- package/package.json +4 -4
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
|
|
@@ -58,9 +66,17 @@ export default meowSite({
|
|
|
58
66
|
|
|
59
67
|
Directories in the configuration are provided explicitly by the application; the default theme resolves from this package and does not depend on the startup directory.
|
|
60
68
|
The whole theme can be replaced with `theme: defineTheme(...)`; all static pages output that theme's root attributes and default options.
|
|
61
|
-
The build writes `theme.cssText` to a temporary CSS file, then lets Bun handle
|
|
62
|
-
so the theme applies without client-side JavaScript.
|
|
63
|
-
|
|
69
|
+
The build writes `theme.cssText` to a temporary CSS file, then lets Bun handle the other assets, and finally delivers through a stylesheet link,
|
|
70
|
+
so the theme applies without client-side JavaScript.
|
|
71
|
+
|
|
72
|
+
`fonts` lists the web fonts the theme names, as Fontsource variable packages
|
|
73
|
+
(`{ family, package, styles }`, the same shape as Meowtail's `journalFonts`). The build publishes
|
|
74
|
+
each package's weight-axis `woff2` files into `fonts/` with a content hash, puts their `@font-face`
|
|
75
|
+
rules first in the theme stylesheet with each subset's `unicode-range`, and copies each license to
|
|
76
|
+
`licenses/<name>-OFL.txt`. A browser fetches only the files a page's text and families use, and
|
|
77
|
+
nothing for a family the page does not use, so a site with a theme switch pays only for the active
|
|
78
|
+
theme. The default Blog theme defaults to `blogFonts` (Geist); any other theme defaults to none.
|
|
79
|
+
Install the listed packages in the site; nothing depends on an external font service.
|
|
64
80
|
Default illustrations use in-package vectors and CSS and require no business images. The `hero`, `feed` and `about` keys of `site.images`
|
|
65
81
|
can explicitly set decorative image paths, for example `images/cover.svg`; paths are relative to the content root's `assets/`, the library handles each page depth,
|
|
66
82
|
and assets are delivered along the existing asset build flow. When no About image is set, the first letter of the site name is used.
|
|
@@ -75,8 +91,8 @@ After installing `meowpress` and its `meowcss` and `meowjsx` dependencies, run i
|
|
|
75
91
|
bunx --no-install meowpress --config ./site.config.ts
|
|
76
92
|
```
|
|
77
93
|
|
|
78
|
-
The CLI requires Bun, and the output location is determined by `outputDirectory`. Font
|
|
79
|
-
|
|
94
|
+
The CLI requires Bun, and the output location is determined by `outputDirectory`. Font packages resolve from the startup directory first, then from
|
|
95
|
+
this package, which carries the default Geist, so consumers are not required to copy source, install workspace-root dependencies or access an external font service.
|
|
80
96
|
|
|
81
97
|
## Independent verification
|
|
82
98
|
|
package/dist/src/build.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type ThemeDefinition } from "meowcss";
|
|
2
|
+
import { type SiteFont } from "./fonts.js";
|
|
2
3
|
import { type SiteIdentity } from "./identity.js";
|
|
3
4
|
import { type ContentSourceInput, type MdxComponent } from "./content.js";
|
|
4
5
|
import { type SitePageProvider } from "./pages.js";
|
|
6
|
+
export { sitePageMarkdownFile } from "./pages.js";
|
|
5
7
|
export type { SitePage, SitePageContext, SitePageProvider } from "./pages.js";
|
|
6
8
|
export type { ContentItem } from "./content.js";
|
|
9
|
+
export type { SiteFont } from "./fonts.js";
|
|
7
10
|
export type { SiteIdentity } from "./identity.js";
|
|
8
11
|
export interface SiteConfig {
|
|
9
12
|
/** One or more roots containing `posts/`, `thoughts/`, and optional assets. */
|
|
@@ -20,10 +23,20 @@ export interface SiteConfig {
|
|
|
20
23
|
site?: Partial<SiteIdentity>;
|
|
21
24
|
/** Consumer-supplied pages; the site writes and indexes them next to its own. */
|
|
22
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;
|
|
23
31
|
/** Extra components available to MDX content pages. */
|
|
24
32
|
mdxComponents?: Record<string, MdxComponent>;
|
|
25
33
|
/** Stylesheet file name for the theme; defaults to "blog-theme.css". */
|
|
26
34
|
themeStylesheet?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Web fonts the theme names, published as separate files with their licenses. Defaults to Geist
|
|
37
|
+
* for the default Blog theme and to none for any other theme.
|
|
38
|
+
*/
|
|
39
|
+
fonts?: readonly SiteFont[];
|
|
27
40
|
}
|
|
28
41
|
export interface ResolvedSiteConfig {
|
|
29
42
|
content: readonly ContentSourceInput[];
|
|
@@ -33,8 +46,10 @@ export interface ResolvedSiteConfig {
|
|
|
33
46
|
projectRootDirectory?: string;
|
|
34
47
|
site: SiteIdentity;
|
|
35
48
|
pages?: SitePageProvider;
|
|
49
|
+
agentGuide?: string;
|
|
36
50
|
mdxComponents: Record<string, MdxComponent>;
|
|
37
51
|
themeStylesheet: string;
|
|
52
|
+
fonts: readonly SiteFont[];
|
|
38
53
|
}
|
|
39
54
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
40
55
|
export declare function meowSite(config: SiteConfig): ResolvedSiteConfig;
|
package/dist/src/build.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { removeLegacyIsFallbacks } from "meowcss/bundle";
|
|
2
|
+
import { blogFonts, blogTheme } from "./theme.js";
|
|
3
|
+
import { FONT_EXTERNAL, publishFonts } from "./fonts.js";
|
|
2
4
|
import { DEFAULT_SITE } from "./identity.js";
|
|
3
|
-
import { mkdir, rm, writeFile, cp } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readFile, rm, writeFile, cp } from "node:fs/promises";
|
|
4
6
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { collectContentAssets, isDraftAsset, loadSection, normalizeContentSources, } from "./content.js";
|
|
7
|
-
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";
|
|
8
9
|
import { loadSkills } from "./skills.js";
|
|
9
|
-
import { sitePageFile } from "./pages.js";
|
|
10
|
+
import { sitePageFile, sitePageMarkdownFile } from "./pages.js";
|
|
11
|
+
export { sitePageMarkdownFile } from "./pages.js";
|
|
10
12
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
11
13
|
export function meowSite(config) {
|
|
12
14
|
return {
|
|
@@ -19,8 +21,10 @@ export function meowSite(config) {
|
|
|
19
21
|
: undefined,
|
|
20
22
|
site: { ...DEFAULT_SITE, ...config.site },
|
|
21
23
|
pages: config.pages,
|
|
24
|
+
agentGuide: config.agentGuide,
|
|
22
25
|
mdxComponents: config.mdxComponents ?? {},
|
|
23
26
|
themeStylesheet: config.themeStylesheet ?? DEFAULT_THEME_STYLESHEET,
|
|
27
|
+
fonts: config.fonts ?? (config.theme === undefined || config.theme === blogTheme ? blogFonts : []),
|
|
24
28
|
};
|
|
25
29
|
}
|
|
26
30
|
async function writePage(generatedDirectory, pathname, html) {
|
|
@@ -56,15 +60,21 @@ async function buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, o
|
|
|
56
60
|
outdir: outputDirectory,
|
|
57
61
|
target: "browser",
|
|
58
62
|
minify: true,
|
|
63
|
+
// Published font files stay separate, so subsets load by unicode-range instead of inline.
|
|
64
|
+
external: [FONT_EXTERNAL],
|
|
59
65
|
});
|
|
60
66
|
if (!result.success) {
|
|
61
67
|
console.error(result.logs);
|
|
62
68
|
throw new Error("Unable to build site static HTML");
|
|
63
69
|
}
|
|
70
|
+
// Bun writes legacy :-webkit-any()/:-moz-any() copies of every :is() rule; Safari misreads them.
|
|
71
|
+
for (const output of result.outputs)
|
|
72
|
+
if (output.path.endsWith(".css"))
|
|
73
|
+
await writeFile(output.path, removeLegacyIsFallbacks(await readFile(output.path, "utf8")));
|
|
64
74
|
}
|
|
65
75
|
/** Consumer pages are written inside the generated directory and never above it. */
|
|
66
|
-
function consumerPageFile(generatedDirectory, path) {
|
|
67
|
-
const file =
|
|
76
|
+
function consumerPageFile(generatedDirectory, path, fileFor = sitePageFile) {
|
|
77
|
+
const file = fileFor(path);
|
|
68
78
|
const target = resolve(generatedDirectory, file);
|
|
69
79
|
const inside = relative(generatedDirectory, target);
|
|
70
80
|
if (!inside || inside.startsWith("..") || isAbsolute(inside)) {
|
|
@@ -79,6 +89,18 @@ async function writeConsumerPages(generatedDirectory, pages) {
|
|
|
79
89
|
}
|
|
80
90
|
return entrypoints;
|
|
81
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
|
+
}
|
|
82
104
|
async function writeContentPages(generatedDirectory, posts, thoughts, theme, site, stylesheet) {
|
|
83
105
|
const entrypoints = [];
|
|
84
106
|
for (const post of posts) {
|
|
@@ -133,18 +155,22 @@ export async function buildSite(input) {
|
|
|
133
155
|
entrypoints.push(await writePage(generatedDirectory, `${skillPath}/index.html`, skillPage(skill, config.theme, config.site, config.themeStylesheet)));
|
|
134
156
|
}
|
|
135
157
|
}
|
|
136
|
-
|
|
158
|
+
// Font faces come first in the one theme stylesheet; the bundled stylesheet sits at the output
|
|
159
|
+
// root, next to the published font directory its rules reference.
|
|
160
|
+
const fontFaces = await publishFonts(config.fonts, config.outputDirectory);
|
|
161
|
+
await writeFile(join(generatedDirectory, config.themeStylesheet), fontFaces ? `${fontFaces}\n${config.theme.cssText}` : config.theme.cssText, "utf8");
|
|
137
162
|
await stageAssets(generatedDirectory, config.content);
|
|
138
163
|
await buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, config.outputDirectory);
|
|
139
|
-
if (config.theme.cssText.includes('"@fontsource-variable/geist"')) {
|
|
140
|
-
const licensesDirectory = join(config.outputDirectory, "licenses");
|
|
141
|
-
await mkdir(licensesDirectory, { recursive: true });
|
|
142
|
-
await cp(fileURLToPath(import.meta.resolve("@fontsource-variable/geist/LICENSE")), join(licensesDirectory, "geist-OFL.txt"));
|
|
143
|
-
}
|
|
144
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;
|
|
145
168
|
await Promise.all([
|
|
146
|
-
writeFile(join(config.outputDirectory, "llm.txt"), llmText(posts, thoughts, config.site, pages), "utf8"),
|
|
147
|
-
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")]),
|
|
148
174
|
writeFile(join(config.outputDirectory, "agents.md"), agentMarkdown(posts, thoughts, config.site, pages), "utf8"),
|
|
149
175
|
]);
|
|
150
176
|
await rm(generatedDirectory, { recursive: true, force: true });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A web font a site loads, as a Fontsource variable package: `family` is the name the theme's
|
|
3
|
+
* stacks use, `package` the npm package with its weight-axis files, and `styles` which of them to
|
|
4
|
+
* load (`italic` loads `wght-italic.css`). The same shape as Meowtail's `ThemeFont`, so a theme's
|
|
5
|
+
* manifest (such as `journalFonts`) can be passed as it is.
|
|
6
|
+
*/
|
|
7
|
+
export interface SiteFont {
|
|
8
|
+
readonly family: string;
|
|
9
|
+
readonly package: string;
|
|
10
|
+
readonly styles: readonly ("normal" | "italic")[];
|
|
11
|
+
}
|
|
12
|
+
/** Font files are published here, next to the bundled stylesheet at the output root. */
|
|
13
|
+
export declare const FONT_DIRECTORY = "fonts";
|
|
14
|
+
/** The bundler leaves these references alone; the build copies the files itself. */
|
|
15
|
+
export declare const FONT_EXTERNAL = "./fonts/*";
|
|
16
|
+
/**
|
|
17
|
+
* Publish each font's files into `<output>/fonts/` with a content hash and its license into
|
|
18
|
+
* `<output>/licenses/`, and return the `@font-face` rules that reference them. Each subset keeps
|
|
19
|
+
* its `unicode-range`, so a browser fetches only the files a page's text and families use.
|
|
20
|
+
*/
|
|
21
|
+
export declare function publishFonts(fonts: readonly SiteFont[], outputDirectory: string): Promise<string>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, cp } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/** Font files are published here, next to the bundled stylesheet at the output root. */
|
|
5
|
+
export const FONT_DIRECTORY = "fonts";
|
|
6
|
+
/** The bundler leaves these references alone; the build copies the files itself. */
|
|
7
|
+
export const FONT_EXTERNAL = `./${FONT_DIRECTORY}/*`;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a package file from the site first (where a host installs its theme's fonts), then from
|
|
10
|
+
* this package (which carries the default Geist).
|
|
11
|
+
*/
|
|
12
|
+
function resolveFontFile(specifier) {
|
|
13
|
+
for (const from of [process.cwd(), dirname(fileURLToPath(import.meta.url))]) {
|
|
14
|
+
try {
|
|
15
|
+
return Bun.resolveSync(specifier, from);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Try the next root.
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Cannot resolve the font file ${specifier}; install its package in the site`);
|
|
22
|
+
}
|
|
23
|
+
const contentHash = (bytes) => new Bun.CryptoHasher("sha256").update(bytes).digest("hex").slice(0, 8);
|
|
24
|
+
/**
|
|
25
|
+
* Publish each font's files into `<output>/fonts/` with a content hash and its license into
|
|
26
|
+
* `<output>/licenses/`, and return the `@font-face` rules that reference them. Each subset keeps
|
|
27
|
+
* its `unicode-range`, so a browser fetches only the files a page's text and families use.
|
|
28
|
+
*/
|
|
29
|
+
export async function publishFonts(fonts, outputDirectory) {
|
|
30
|
+
if (!fonts.length)
|
|
31
|
+
return "";
|
|
32
|
+
const fontDirectory = join(outputDirectory, FONT_DIRECTORY);
|
|
33
|
+
const licenseDirectory = join(outputDirectory, "licenses");
|
|
34
|
+
await mkdir(fontDirectory, { recursive: true });
|
|
35
|
+
await mkdir(licenseDirectory, { recursive: true });
|
|
36
|
+
const rules = [];
|
|
37
|
+
for (const font of fonts) {
|
|
38
|
+
for (const style of font.styles) {
|
|
39
|
+
const sheet = resolveFontFile(`${font.package}/${style === "italic" ? "wght-italic" : "wght"}.css`);
|
|
40
|
+
const source = await readFile(sheet, "utf8");
|
|
41
|
+
const files = new Map();
|
|
42
|
+
for (const [, path] of source.matchAll(/url\(\.\/(files\/[^)]+\.woff2)\)/g)) {
|
|
43
|
+
if (files.has(path))
|
|
44
|
+
continue;
|
|
45
|
+
const bytes = await readFile(join(dirname(sheet), path));
|
|
46
|
+
const name = `${basename(path, ".woff2")}-${contentHash(bytes)}.woff2`;
|
|
47
|
+
await writeFile(join(fontDirectory, name), bytes);
|
|
48
|
+
files.set(path, name);
|
|
49
|
+
}
|
|
50
|
+
rules.push(source.replace(/url\(\.\/(files\/[^)]+\.woff2)\)/g, (_match, path) => `url(./${FONT_DIRECTORY}/${files.get(path)})`));
|
|
51
|
+
}
|
|
52
|
+
const license = resolveFontFile(`${font.package}/LICENSE`);
|
|
53
|
+
await cp(license, join(licenseDirectory, `${font.package.split("/").pop()}-OFL.txt`));
|
|
54
|
+
}
|
|
55
|
+
return rules.join("\n");
|
|
56
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { collectContentAssets, collectAssetFiles, isDraftAsset, loadSection, nor
|
|
|
2
2
|
export { buildSite, meowSite, type ResolvedSiteConfig, type SiteConfig } from "./build.js";
|
|
3
3
|
export { aboutPage, agentMarkdown, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
4
4
|
export { DEFAULT_SITE, type SiteAbout, type SiteIdentity, type SiteProfileLink } from "./identity.js";
|
|
5
|
-
export { blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
5
|
+
export { blogFonts, blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
6
|
+
export type { SiteFont } from "./fonts.js";
|
|
6
7
|
export { loadSkills, type LoadSkillsOptions, type Skill, type SkillFile, type SkillScope, } from "./skills.js";
|
package/dist/src/index.js
CHANGED
|
@@ -2,5 +2,5 @@ export { collectContentAssets, collectAssetFiles, isDraftAsset, loadSection, nor
|
|
|
2
2
|
export { buildSite, meowSite } from "./build.js";
|
|
3
3
|
export { aboutPage, agentMarkdown, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
4
4
|
export { DEFAULT_SITE } from "./identity.js";
|
|
5
|
-
export { blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
5
|
+
export { blogFonts, blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
6
6
|
export { loadSkills, } from "./skills.js";
|
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/dist/src/theme.d.ts
CHANGED
|
@@ -37,9 +37,11 @@ export declare const blogTokens: import("meowcss").TokenRefs<{
|
|
|
37
37
|
readonly fontMono: "\"SF Mono\", \"Cascadia Code\", Menlo, Consolas, ui-monospace, monospace";
|
|
38
38
|
readonly paperGrain: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.78' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23grain)' opacity='.065'/%3E%3C/svg%3E\")";
|
|
39
39
|
}> & import("meowcss").TokenSet;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
/** Static HTML owns the theme root; CSS remains available without JavaScript. */
|
|
41
|
+
export declare const blogTheme: import("meowcss").ThemeDefinition;
|
|
42
|
+
/** The web font the Blog theme names; the build publishes it with its license. */
|
|
43
|
+
export declare const blogFonts: readonly [{
|
|
44
|
+
readonly family: "Geist Variable";
|
|
45
|
+
readonly package: "@fontsource-variable/geist";
|
|
46
|
+
readonly styles: readonly ["normal"];
|
|
47
|
+
}];
|
package/dist/src/theme.js
CHANGED
|
@@ -2217,14 +2217,13 @@ const styles = css `
|
|
|
2217
2217
|
}
|
|
2218
2218
|
`;
|
|
2219
2219
|
/** Static HTML owns the theme root; CSS remains available without JavaScript. */
|
|
2220
|
-
const
|
|
2220
|
+
export const blogTheme = defineTheme({
|
|
2221
2221
|
options: blogOptions,
|
|
2222
2222
|
defaults: { scheme: "auto" },
|
|
2223
2223
|
tokens: [blogTokens],
|
|
2224
2224
|
styles,
|
|
2225
2225
|
});
|
|
2226
|
-
|
|
2227
|
-
export const
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
});
|
|
2226
|
+
/** The web font the Blog theme names; the build publishes it with its license. */
|
|
2227
|
+
export const blogFonts = [
|
|
2228
|
+
{ family: "Geist Variable", package: "@fontsource-variable/geist", styles: ["normal"] },
|
|
2229
|
+
];
|
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": {
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"yaml": "^2.9.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"meowcss": "^0.3.0-alpha.
|
|
40
|
+
"meowcss": "^0.3.0-alpha.1",
|
|
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
|
}
|