meowpress 0.1.1-alpha.0 → 0.2.0-alpha.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 +60 -29
- package/dist/src/build.d.ts +14 -1
- package/dist/src/build.js +66 -26
- package/dist/src/content.d.ts +5 -0
- package/dist/src/content.js +7 -7
- package/dist/src/identity.js +1 -1
- package/dist/src/pages.d.ts +28 -0
- package/dist/src/pages.js +10 -0
- package/dist/src/site.d.ts +12 -9
- package/dist/src/site.js +168 -130
- package/dist/src/skills.js +18 -18
- package/dist/src/theme.js +7 -7
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Meowpress
|
|
2
2
|
|
|
3
|
-
`meowpress`
|
|
4
|
-
|
|
3
|
+
`meowpress` provides content reading, page rendering, themes and the build pipeline for static sites.
|
|
4
|
+
Consumers own the site configuration and content and build through this package; the package does not own the application's posts or output directory.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Published history: `0.1.1-alpha.0` was accepted from the registry, followed by `0.1.1-alpha.1` with the English site and CLI defaults; `latest` remained `0.1.0`. The current workspace package is `0.2.0-alpha.0`, and its required CSS and JSX peer ranges accept `^0.2.0-alpha.0`. The host selects the shared CSS/JSX modules, and this package does not privately install another CSS copy; it does not depend on Meowtail or MeowSlides. Out-of-package builds and theme identity acceptance are bound to exact package archives, and peer contracts are not bypassed through overrides. Publishing requires separate explicit authorization.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
HTML
|
|
8
|
+
This package is a standalone content publishing library with source in `packages/meowpress`; it already has a public build entry, independent tests and typechecking,
|
|
9
|
+
and does not depend on Assistant, WorkCell, Deno or the content and runtime state of a specific Blog. Style generation comes from `meowcss`,
|
|
10
|
+
and HTML rendering comes from `meowjsx/web`; MeowUI does not need to be installed.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Entries
|
|
13
13
|
|
|
14
|
-
- `meowpress/build
|
|
15
|
-
|
|
16
|
-
- `meowpress --config ./site.config.ts
|
|
14
|
+
- `meowpress/build`: `meowSite()` defines configuration and `buildSite()` performs the build; this is the only public export.
|
|
15
|
+
Content reading, page rendering, skill indexing and themes are internal package modules.
|
|
16
|
+
- `meowpress --config ./site.config.ts`: Bun CLI that loads the consumer-provided configuration and builds.
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
19
|
import { meowSite } from "meowpress/build";
|
|
@@ -25,31 +25,62 @@ export default meowSite({
|
|
|
25
25
|
});
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
可通过 `theme: defineTheme(...)` 替换整套主题;所有静态页面输出该主题的根属性和默认选项。
|
|
30
|
-
构建将 `theme.cssText` 写入临时 CSS,再由 Bun 处理字体与其他资源,最终通过 stylesheet 链接交付,
|
|
31
|
-
无需客户端 JavaScript 即可应用主题。默认字体 Geist 的文件随站点打包,不依赖外部字体服务。
|
|
32
|
-
使用默认字体时,输出同时包含 `licenses/geist-OFL.txt`,保留字体的 SIL OFL 许可。
|
|
33
|
-
默认插画使用包内矢量与 CSS,不要求业务图片。可用 `site.images` 的 `hero`、`feed`、`about`
|
|
34
|
-
显式设置装饰图路径,例如 `images/cover.svg`;路径相对内容根的 `assets/`,库负责各页面深度,
|
|
35
|
-
资源沿现有资产构建流程交付。未设置 About 图片时使用站点名首字母。
|
|
36
|
-
内容源包含 `posts/`、`thoughts/` 和可选资源;以下划线开头的草稿不进入发布输出。
|
|
37
|
-
站点有自己的主题,不隐式安装 MeowUI 的应用主题。
|
|
28
|
+
## Pages a consumer owns
|
|
38
29
|
|
|
39
|
-
|
|
30
|
+
A consumer that supplies `pages` owns the whole site map. The provider receives the content that was
|
|
31
|
+
already loaded and returns complete documents; the package writes them at their paths, adds them to
|
|
32
|
+
the agent-facing indexes, and does not publish its own blog routes next to them. One site map, one
|
|
33
|
+
owner.
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
```ts
|
|
36
|
+
import { meowSite, type SitePage } from "meowpress/build";
|
|
37
|
+
|
|
38
|
+
export default meowSite({
|
|
39
|
+
content: [{ directory: "/path/to/content" }],
|
|
40
|
+
outputDirectory: "/path/to/dist",
|
|
41
|
+
themeStylesheet: "site-theme.css",
|
|
42
|
+
pages: ({ posts, theme }): SitePage[] =>
|
|
43
|
+
posts.map((post) => ({
|
|
44
|
+
path: post.slug,
|
|
45
|
+
title: post.title,
|
|
46
|
+
html: renderDocument(post, theme),
|
|
47
|
+
})),
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `pages` is a provider of complete HTML documents. A path that resolves above the site root is
|
|
52
|
+
refused rather than clamped.
|
|
53
|
+
- `mdxComponents` adds named components to the MDX content pages; the built-in `Callout` stays
|
|
54
|
+
available.
|
|
55
|
+
- `themeStylesheet` names the theme stylesheet the build writes, so a consumer page can link it. It
|
|
56
|
+
applies to the built-in pages as well, so a renamed stylesheet never leaves a page linking a file
|
|
57
|
+
that was not written.
|
|
58
|
+
|
|
59
|
+
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
|
+
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 fonts and other assets, and finally delivers through a stylesheet link,
|
|
62
|
+
so the theme applies without client-side JavaScript. Files for the default Geist font are bundled with the site and do not depend on an external font service.
|
|
63
|
+
When the default font is used, the output also includes `licenses/geist-OFL.txt`, preserving the font's SIL OFL license.
|
|
64
|
+
Default illustrations use in-package vectors and CSS and require no business images. The `hero`, `feed` and `about` keys of `site.images`
|
|
65
|
+
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
|
+
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.
|
|
67
|
+
The content source contains `posts/`, `thoughts/` and optional assets; drafts whose names start with an underscore do not enter the published output.
|
|
68
|
+
The site has its own theme and does not implicitly install MeowUI's application theme.
|
|
69
|
+
|
|
70
|
+
## Verification and build
|
|
71
|
+
|
|
72
|
+
After installing `meowpress` and its `meowcss` and `meowjsx` dependencies, run in the consumer directory:
|
|
42
73
|
|
|
43
74
|
```sh
|
|
44
75
|
bunx --no-install meowpress --config ./site.config.ts
|
|
45
76
|
```
|
|
46
77
|
|
|
47
|
-
CLI
|
|
48
|
-
|
|
78
|
+
The CLI requires Bun, and the output location is determined by `outputDirectory`. Font files resolve from this package's official Geist dependency,
|
|
79
|
+
and consumers are not required to copy source, install workspace-root dependencies or access an external font service.
|
|
49
80
|
|
|
50
|
-
##
|
|
81
|
+
## Independent verification
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
`bun run --cwd packages/meowpress typecheck
|
|
54
|
-
`meowpress/build`
|
|
55
|
-
|
|
83
|
+
Once repository dependencies are ready, run `bun run --cwd packages/meowpress test` and
|
|
84
|
+
`bun run --cwd packages/meowpress typecheck`. Tests live in `tests/unit/`; real build tests create temporary content, themes and images through the public
|
|
85
|
+
`meowpress/build` entry,
|
|
86
|
+
covering Markdown, MDX, Thought output and draft page/asset exclusion, without reading Blog content or invoking the Blog build.
|
package/dist/src/build.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type ThemeDefinition } from "meowcss";
|
|
2
2
|
import { type SiteIdentity } from "./identity.js";
|
|
3
|
-
import { type ContentSourceInput } from "./content.js";
|
|
3
|
+
import { type ContentSourceInput, type MdxComponent } from "./content.js";
|
|
4
|
+
import { type SitePageProvider } from "./pages.js";
|
|
5
|
+
export type { SitePage, SitePageContext, SitePageProvider } from "./pages.js";
|
|
6
|
+
export type { ContentItem } from "./content.js";
|
|
7
|
+
export type { SiteIdentity } from "./identity.js";
|
|
4
8
|
export interface SiteConfig {
|
|
5
9
|
/** One or more roots containing `posts/`, `thoughts/`, and optional assets. */
|
|
6
10
|
content: ContentSourceInput | readonly ContentSourceInput[];
|
|
@@ -14,6 +18,12 @@ export interface SiteConfig {
|
|
|
14
18
|
projectRootDirectory?: string;
|
|
15
19
|
/** Site name, description, repository links and About copy; unset fields use neutral defaults. */
|
|
16
20
|
site?: Partial<SiteIdentity>;
|
|
21
|
+
/** Consumer-supplied pages; the site writes and indexes them next to its own. */
|
|
22
|
+
pages?: SitePageProvider;
|
|
23
|
+
/** Extra components available to MDX content pages. */
|
|
24
|
+
mdxComponents?: Record<string, MdxComponent>;
|
|
25
|
+
/** Stylesheet file name for the theme; defaults to "blog-theme.css". */
|
|
26
|
+
themeStylesheet?: string;
|
|
17
27
|
}
|
|
18
28
|
export interface ResolvedSiteConfig {
|
|
19
29
|
content: readonly ContentSourceInput[];
|
|
@@ -22,6 +32,9 @@ export interface ResolvedSiteConfig {
|
|
|
22
32
|
theme: ThemeDefinition;
|
|
23
33
|
projectRootDirectory?: string;
|
|
24
34
|
site: SiteIdentity;
|
|
35
|
+
pages?: SitePageProvider;
|
|
36
|
+
mdxComponents: Record<string, MdxComponent>;
|
|
37
|
+
themeStylesheet: string;
|
|
25
38
|
}
|
|
26
39
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
27
40
|
export declare function meowSite(config: SiteConfig): ResolvedSiteConfig;
|
package/dist/src/build.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { blogTheme } from "./theme.js";
|
|
2
2
|
import { DEFAULT_SITE } from "./identity.js";
|
|
3
3
|
import { mkdir, rm, writeFile, cp } from "node:fs/promises";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
4
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { collectContentAssets, isDraftAsset, loadSection, normalizeContentSources, } from "./content.js";
|
|
7
|
-
import { aboutPage, agentMarkdown, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
7
|
+
import { aboutPage, agentMarkdown, DEFAULT_THEME_STYLESHEET, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
8
8
|
import { loadSkills } from "./skills.js";
|
|
9
|
+
import { sitePageFile } from "./pages.js";
|
|
9
10
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
10
11
|
export function meowSite(config) {
|
|
11
12
|
return {
|
|
@@ -17,6 +18,9 @@ export function meowSite(config) {
|
|
|
17
18
|
? resolve(config.projectRootDirectory)
|
|
18
19
|
: undefined,
|
|
19
20
|
site: { ...DEFAULT_SITE, ...config.site },
|
|
21
|
+
pages: config.pages,
|
|
22
|
+
mdxComponents: config.mdxComponents ?? {},
|
|
23
|
+
themeStylesheet: config.themeStylesheet ?? DEFAULT_THEME_STYLESHEET,
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
async function writePage(generatedDirectory, pathname, html) {
|
|
@@ -58,13 +62,30 @@ async function buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, o
|
|
|
58
62
|
throw new Error("Unable to build site static HTML");
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
|
-
|
|
65
|
+
/** Consumer pages are written inside the generated directory and never above it. */
|
|
66
|
+
function consumerPageFile(generatedDirectory, path) {
|
|
67
|
+
const file = sitePageFile(path);
|
|
68
|
+
const target = resolve(generatedDirectory, file);
|
|
69
|
+
const inside = relative(generatedDirectory, target);
|
|
70
|
+
if (!inside || inside.startsWith("..") || isAbsolute(inside)) {
|
|
71
|
+
throw new Error(`Site page path stays inside the site root: ${path}`);
|
|
72
|
+
}
|
|
73
|
+
return file;
|
|
74
|
+
}
|
|
75
|
+
async function writeConsumerPages(generatedDirectory, pages) {
|
|
76
|
+
const entrypoints = [];
|
|
77
|
+
for (const page of pages) {
|
|
78
|
+
entrypoints.push(await writePage(generatedDirectory, consumerPageFile(generatedDirectory, page.path), page.html));
|
|
79
|
+
}
|
|
80
|
+
return entrypoints;
|
|
81
|
+
}
|
|
82
|
+
async function writeContentPages(generatedDirectory, posts, thoughts, theme, site, stylesheet) {
|
|
62
83
|
const entrypoints = [];
|
|
63
84
|
for (const post of posts) {
|
|
64
|
-
entrypoints.push(await writePage(generatedDirectory, `posts/${post.slug}/index.html`, postPage(post, posts, thoughts, theme, site)));
|
|
85
|
+
entrypoints.push(await writePage(generatedDirectory, `posts/${post.slug}/index.html`, postPage(post, posts, thoughts, theme, site, stylesheet)));
|
|
65
86
|
}
|
|
66
87
|
for (const thought of thoughts) {
|
|
67
|
-
entrypoints.push(await writePage(generatedDirectory, `thoughts/${thought.slug}/index.html`, thoughtPage(thought, theme, site)));
|
|
88
|
+
entrypoints.push(await writePage(generatedDirectory, `thoughts/${thought.slug}/index.html`, thoughtPage(thought, theme, site, stylesheet)));
|
|
68
89
|
}
|
|
69
90
|
return entrypoints;
|
|
70
91
|
}
|
|
@@ -72,30 +93,47 @@ export async function buildSite(input) {
|
|
|
72
93
|
const config = meowSite(input);
|
|
73
94
|
const generatedDirectory = resolve(config.outputDirectory, "../.generated");
|
|
74
95
|
const [posts, thoughts, skills] = await Promise.all([
|
|
75
|
-
loadSection(config.content, "post"),
|
|
76
|
-
loadSection(config.content, "thought"),
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
loadSection(config.content, "post", { components: config.mdxComponents }),
|
|
97
|
+
loadSection(config.content, "thought", { components: config.mdxComponents }),
|
|
98
|
+
config.pages
|
|
99
|
+
? Promise.resolve([])
|
|
100
|
+
: loadSkills(config.skillsDirectory, {
|
|
101
|
+
projectRootDirectory: config.projectRootDirectory,
|
|
102
|
+
}),
|
|
80
103
|
]);
|
|
81
104
|
const timeline = [...posts, ...thoughts].sort((left, right) => (right.date?.getTime() ?? 0) - (left.date?.getTime() ?? 0));
|
|
82
105
|
await rm(config.outputDirectory, { recursive: true, force: true });
|
|
83
106
|
await rm(generatedDirectory, { recursive: true, force: true });
|
|
84
107
|
await mkdir(config.outputDirectory, { recursive: true });
|
|
85
108
|
await mkdir(generatedDirectory, { recursive: true });
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
// A consumer that supplies its own pages owns the whole site map; the package's
|
|
110
|
+
// own blog routes are not published alongside them.
|
|
111
|
+
const pages = config.pages
|
|
112
|
+
? await config.pages({
|
|
113
|
+
posts,
|
|
114
|
+
thoughts,
|
|
115
|
+
site: config.site,
|
|
116
|
+
theme: config.theme,
|
|
117
|
+
})
|
|
118
|
+
: undefined;
|
|
119
|
+
const entrypoints = [];
|
|
120
|
+
if (pages) {
|
|
121
|
+
entrypoints.push(...(await writeConsumerPages(generatedDirectory, pages)));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
entrypoints.push(...(await Promise.all([
|
|
125
|
+
writePage(generatedDirectory, "index.html", homePage(timeline, config.theme, config.site, config.themeStylesheet)),
|
|
126
|
+
writePage(generatedDirectory, "about/index.html", aboutPage(config.theme, config.site, config.themeStylesheet)),
|
|
127
|
+
writePage(generatedDirectory, "posts/index.html", listPage("post", posts, config.theme, config.site, config.themeStylesheet)),
|
|
128
|
+
writePage(generatedDirectory, "thoughts/index.html", listPage("thought", thoughts, config.theme, config.site, config.themeStylesheet)),
|
|
129
|
+
writePage(generatedDirectory, "skills/index.html", skillsIndexPage(skills, config.theme, config.site, config.themeStylesheet)),
|
|
130
|
+
])), ...(await writeContentPages(generatedDirectory, posts, thoughts, config.theme, config.site, config.themeStylesheet)));
|
|
131
|
+
for (const skill of skills) {
|
|
132
|
+
const skillPath = skill.scope === "public" ? `skills/${skill.slug}` : `skills/project/${skill.slug}`;
|
|
133
|
+
entrypoints.push(await writePage(generatedDirectory, `${skillPath}/index.html`, skillPage(skill, config.theme, config.site, config.themeStylesheet)));
|
|
134
|
+
}
|
|
97
135
|
}
|
|
98
|
-
await writeFile(join(generatedDirectory,
|
|
136
|
+
await writeFile(join(generatedDirectory, config.themeStylesheet), config.theme.cssText.replaceAll('"@fontsource-variable/geist"', JSON.stringify(fileURLToPath(import.meta.resolve("@fontsource-variable/geist")))), "utf8");
|
|
99
137
|
await stageAssets(generatedDirectory, config.content);
|
|
100
138
|
await buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, config.outputDirectory);
|
|
101
139
|
if (config.theme.cssText.includes('"@fontsource-variable/geist"')) {
|
|
@@ -105,10 +143,12 @@ export async function buildSite(input) {
|
|
|
105
143
|
}
|
|
106
144
|
await copyPostAssets(config.outputDirectory, config.content);
|
|
107
145
|
await Promise.all([
|
|
108
|
-
writeFile(join(config.outputDirectory, "llm.txt"), llmText(posts, thoughts, config.site), "utf8"),
|
|
109
|
-
writeFile(join(config.outputDirectory, "llms.txt"), llmText(posts, thoughts, config.site), "utf8"),
|
|
110
|
-
writeFile(join(config.outputDirectory, "agents.md"), agentMarkdown(posts, thoughts, config.site), "utf8"),
|
|
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"),
|
|
148
|
+
writeFile(join(config.outputDirectory, "agents.md"), agentMarkdown(posts, thoughts, config.site, pages), "utf8"),
|
|
111
149
|
]);
|
|
112
150
|
await rm(generatedDirectory, { recursive: true, force: true });
|
|
113
|
-
console.log(
|
|
151
|
+
console.log(pages
|
|
152
|
+
? `Built ${posts.length} content items and ${pages.length} site pages into site dist/.`
|
|
153
|
+
: `Built ${posts.length} posts and ${thoughts.length} thoughts into site dist/.`);
|
|
114
154
|
}
|
package/dist/src/content.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Child, type Props } from "meowjsx/runtime";
|
|
1
2
|
export type ContentKind = "post" | "thought";
|
|
2
3
|
export type Frontmatter = Record<string, unknown>;
|
|
3
4
|
/** A content root containing `posts/`, `thoughts/`, and optional asset folders. */
|
|
@@ -11,7 +12,11 @@ export type DuplicateContentPolicy = "first-wins" | "error";
|
|
|
11
12
|
export interface LoadSectionOptions {
|
|
12
13
|
/** The first source wins by default, so the source order is an explicit overlay order. */
|
|
13
14
|
duplicate?: DuplicateContentPolicy;
|
|
15
|
+
/** Extra components available to MDX content pages. */
|
|
16
|
+
components?: Record<string, MdxComponent>;
|
|
14
17
|
}
|
|
18
|
+
/** A component an MDX content page may use by name. */
|
|
19
|
+
export type MdxComponent = (props: Props) => Child;
|
|
15
20
|
export interface ContentItem {
|
|
16
21
|
kind: ContentKind;
|
|
17
22
|
slug: string;
|
package/dist/src/content.js
CHANGED
|
@@ -112,7 +112,7 @@ function Callout(props) {
|
|
|
112
112
|
const MDX_COMPONENTS = {
|
|
113
113
|
Callout,
|
|
114
114
|
};
|
|
115
|
-
async function renderMdx(source) {
|
|
115
|
+
async function renderMdx(source, components) {
|
|
116
116
|
const compiled = await compile({ value: source }, {
|
|
117
117
|
outputFormat: "function-body",
|
|
118
118
|
remarkPlugins: [remarkGfm],
|
|
@@ -123,15 +123,15 @@ async function renderMdx(source) {
|
|
|
123
123
|
jsxs: h,
|
|
124
124
|
});
|
|
125
125
|
const component = module.default;
|
|
126
|
-
return renderToHtml(component({ components: MDX_COMPONENTS }));
|
|
126
|
+
return renderToHtml(component({ components: { ...MDX_COMPONENTS, ...components } }));
|
|
127
127
|
}
|
|
128
|
-
async function renderSource(source, sourcePath) {
|
|
128
|
+
async function renderSource(source, sourcePath, components) {
|
|
129
129
|
if (sourcePath.endsWith(".md")) {
|
|
130
130
|
return Bun.markdown.html(source, {
|
|
131
131
|
tagFilter: true,
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
-
return renderMdx(source);
|
|
134
|
+
return renderMdx(source, components);
|
|
135
135
|
}
|
|
136
136
|
async function collectFiles(directory) {
|
|
137
137
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
@@ -186,7 +186,7 @@ export function normalizeContentSources(input) {
|
|
|
186
186
|
function duplicateContentError(kind, slug) {
|
|
187
187
|
return new Error(`Duplicate ${kind} slug "${slug}" while merging content sources.`);
|
|
188
188
|
}
|
|
189
|
-
async function loadSectionFromSource(source, kind) {
|
|
189
|
+
async function loadSectionFromSource(source, kind, options) {
|
|
190
190
|
const sectionDirectory = resolve(source.directory, `${kind}s`);
|
|
191
191
|
let sourceFiles;
|
|
192
192
|
try {
|
|
@@ -217,7 +217,7 @@ async function loadSectionFromSource(source, kind) {
|
|
|
217
217
|
const bodyWithoutLeadingHeading = document.body.replace(/^\s*#\s+.+?(?:\r?\n|$)+/, "");
|
|
218
218
|
const plainText = stripMarkdown(bodyWithoutLeadingHeading);
|
|
219
219
|
const title = asText(document.metadata.title) ?? titleFromBody(document.body, slug);
|
|
220
|
-
const html = withoutLeadingHeading(await renderSource(document.body, sourcePath));
|
|
220
|
+
const html = withoutLeadingHeading(await renderSource(document.body, sourcePath, options.components ?? {}));
|
|
221
221
|
items.push({
|
|
222
222
|
kind,
|
|
223
223
|
slug,
|
|
@@ -241,7 +241,7 @@ export async function loadSection(sources, kind, options = {}) {
|
|
|
241
241
|
const merged = new Map();
|
|
242
242
|
const duplicatePolicy = options.duplicate ?? "first-wins";
|
|
243
243
|
for (const source of normalizeContentSources(sources)) {
|
|
244
|
-
const items = await loadSectionFromSource(source, kind);
|
|
244
|
+
const items = await loadSectionFromSource(source, kind, options);
|
|
245
245
|
for (const item of items) {
|
|
246
246
|
if (merged.has(item.slug)) {
|
|
247
247
|
if (duplicatePolicy === "error")
|
package/dist/src/identity.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ThemeDefinition } from "meowcss";
|
|
2
|
+
import type { ContentItem } from "./content.js";
|
|
3
|
+
import type { SiteIdentity } from "./identity.js";
|
|
4
|
+
/** A page supplied by the consumer of the site. */
|
|
5
|
+
export interface SitePage {
|
|
6
|
+
/** Route path below the site root; "packages/meowtail" becomes /packages/meowtail/. */
|
|
7
|
+
path: string;
|
|
8
|
+
title: string;
|
|
9
|
+
/** A complete HTML document for the route. */
|
|
10
|
+
html: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
/** Grouping used by the site index and the agent-facing indexes. */
|
|
13
|
+
group?: string;
|
|
14
|
+
order?: number;
|
|
15
|
+
}
|
|
16
|
+
/** What a page provider receives when the site is built. */
|
|
17
|
+
export interface SitePageContext {
|
|
18
|
+
posts: readonly ContentItem[];
|
|
19
|
+
thoughts: readonly ContentItem[];
|
|
20
|
+
site: SiteIdentity;
|
|
21
|
+
theme: ThemeDefinition;
|
|
22
|
+
}
|
|
23
|
+
/** Supplies the consumer's own pages for the site. */
|
|
24
|
+
export type SitePageProvider = (context: SitePageContext) => readonly SitePage[] | Promise<readonly SitePage[]>;
|
|
25
|
+
/** The route a page is published under, with a leading and a trailing slash. */
|
|
26
|
+
export declare function sitePageUrl(path: string): string;
|
|
27
|
+
/** The output file a page is written to, relative to the site root. */
|
|
28
|
+
export declare function sitePageFile(path: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The route a page is published under, with a leading and a trailing slash. */
|
|
2
|
+
export function sitePageUrl(path) {
|
|
3
|
+
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
4
|
+
return normalized ? `/${normalized}/` : "/";
|
|
5
|
+
}
|
|
6
|
+
/** The output file a page is written to, relative to the site root. */
|
|
7
|
+
export function sitePageFile(path) {
|
|
8
|
+
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
9
|
+
return normalized ? `${normalized}/index.html` : "index.html";
|
|
10
|
+
}
|
package/dist/src/site.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { type ThemeDefinition } from "meowcss";
|
|
2
2
|
import { type ContentItem } from "./content.js";
|
|
3
3
|
import { type SiteIdentity } from "./identity.js";
|
|
4
|
+
import { type SitePage } from "./pages.js";
|
|
4
5
|
import type { Skill } from "./skills.js";
|
|
5
|
-
|
|
6
|
-
export declare
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function
|
|
6
|
+
/** The name of the theme stylesheet the package writes by default. */
|
|
7
|
+
export declare const DEFAULT_THEME_STYLESHEET = "blog-theme.css";
|
|
8
|
+
export declare function homePage(items: ContentItem[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
9
|
+
export declare function listPage(kind: "post" | "thought", items: ContentItem[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
10
|
+
export declare function postPage(item: ContentItem, posts: ContentItem[], thoughts: ContentItem[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
11
|
+
export declare function thoughtPage(item: ContentItem, theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
12
|
+
export declare function aboutPage(theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
13
|
+
export declare function skillsIndexPage(skills: Skill[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
14
|
+
export declare function skillPage(skill: Skill, theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
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;
|
package/dist/src/site.js
CHANGED
|
@@ -4,13 +4,24 @@ 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 { sitePageUrl } from "./pages.js";
|
|
7
8
|
import { layoutSkillGraph } from "./skill-graph.js";
|
|
9
|
+
/** The name of the theme stylesheet the package writes by default. */
|
|
10
|
+
export const DEFAULT_THEME_STYLESHEET = "blog-theme.css";
|
|
11
|
+
/** The href of the theme stylesheet for a page at a given depth. */
|
|
12
|
+
function styleHref(depth, stylesheet) {
|
|
13
|
+
return `${"../".repeat(depth)}${stylesheet}`;
|
|
14
|
+
}
|
|
15
|
+
/** The number of directories a slug puts a page below its section. */
|
|
16
|
+
function slugDepth(slug) {
|
|
17
|
+
return slug.split("/").filter(Boolean).length;
|
|
18
|
+
}
|
|
8
19
|
function SiteHeader({ activePath, site }) {
|
|
9
20
|
return (h("header", { className: "site" },
|
|
10
21
|
h("a", { className: "site-name", href: "/" }, site.name),
|
|
11
|
-
h("nav", { className: "site-nav", "aria-label": "
|
|
22
|
+
h("nav", { className: "site-nav", "aria-label": "Primary navigation" },
|
|
12
23
|
h("a", { className: activePath === "/skills/" ? "active" : "", href: "/skills/" }, "Skills"),
|
|
13
|
-
h("a", { className: activePath === "/about/" ? "active" : "", href: "/about/" }, "
|
|
24
|
+
h("a", { className: activePath === "/about/" ? "active" : "", href: "/about/" }, "About"))));
|
|
14
25
|
}
|
|
15
26
|
function SiteFooter({ site }) {
|
|
16
27
|
return (h("footer", { className: "site" },
|
|
@@ -26,7 +37,7 @@ function SiteFooter({ site }) {
|
|
|
26
37
|
}
|
|
27
38
|
function Document({ title, description, activePath, styleHref, children, theme, site, }) {
|
|
28
39
|
const documentTitle = title === site.name ? site.name : `${title} · ${site.name}`;
|
|
29
|
-
return (h("html", { lang: "
|
|
40
|
+
return (h("html", { lang: "en", ...themeAttributes(theme) },
|
|
30
41
|
h("head", null,
|
|
31
42
|
h("meta", { charSet: "UTF-8" }),
|
|
32
43
|
h("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
@@ -46,15 +57,15 @@ function pageHtml(props) {
|
|
|
46
57
|
}
|
|
47
58
|
function dateLabel(dateKey) {
|
|
48
59
|
if (dateKey === "undated")
|
|
49
|
-
return { date: "
|
|
60
|
+
return { date: "Undated", weekday: "" };
|
|
50
61
|
const date = new Date(`${dateKey}T00:00:00+08:00`);
|
|
51
62
|
return {
|
|
52
|
-
date: new Intl.DateTimeFormat("
|
|
63
|
+
date: new Intl.DateTimeFormat("en-US", {
|
|
53
64
|
timeZone: "Asia/Shanghai",
|
|
54
65
|
month: "long",
|
|
55
66
|
day: "numeric",
|
|
56
67
|
}).format(date),
|
|
57
|
-
weekday: new Intl.DateTimeFormat("
|
|
68
|
+
weekday: new Intl.DateTimeFormat("en-US", {
|
|
58
69
|
timeZone: "Asia/Shanghai",
|
|
59
70
|
weekday: "short",
|
|
60
71
|
year: "numeric",
|
|
@@ -68,9 +79,9 @@ function Hero({ image }) {
|
|
|
68
79
|
return (h("section", { className: "hero", "aria-labelledby": "hero-title" },
|
|
69
80
|
h("div", { className: "hero-copy" },
|
|
70
81
|
h("h1", { id: "hero-title" },
|
|
71
|
-
"
|
|
82
|
+
"Ten thousand years is too long;",
|
|
72
83
|
h("br", null),
|
|
73
|
-
h("em", null, "
|
|
84
|
+
h("em", null, "only this day is true."))),
|
|
74
85
|
h("div", { className: "hero-work", "aria-hidden": "true" },
|
|
75
86
|
h("svg", { className: "hero-doodle", viewBox: "0 0 260 190" },
|
|
76
87
|
h("path", { className: "doodle-loop", d: "M24 96C20 45 58 18 122 20c72 2 116 33 114 84-2 48-48 70-113 68-65-2-96-27-99-76Z" }),
|
|
@@ -104,7 +115,7 @@ function ThoughtCard({ item, linked = false }) {
|
|
|
104
115
|
const body = (h(Fragment, null,
|
|
105
116
|
h("div", { className: "time" },
|
|
106
117
|
h("span", { className: "t" }, item.timeLabel || item.dateKey),
|
|
107
|
-
" \u00B7
|
|
118
|
+
" \u00B7 Thought"),
|
|
108
119
|
h("div", { className: "body", dangerouslySetInnerHTML: { __html: withoutLeadingHeading(item.html) } }),
|
|
109
120
|
h(Tags, { item: item })));
|
|
110
121
|
if (!linked)
|
|
@@ -113,18 +124,18 @@ function ThoughtCard({ item, linked = false }) {
|
|
|
113
124
|
h("a", { className: "thought-link", href: itemUrl(item) }, body)));
|
|
114
125
|
}
|
|
115
126
|
function PostCard({ item }) {
|
|
116
|
-
const category = typeof item.metadata.category === "string" ? item.metadata.category : "
|
|
127
|
+
const category = typeof item.metadata.category === "string" ? item.metadata.category : "Post";
|
|
117
128
|
return (h("a", { className: "post-card", href: itemUrl(item), "data-kind": "post" },
|
|
118
129
|
h("div", { className: "card-top" },
|
|
119
130
|
h("span", { className: "kind" }, category),
|
|
120
131
|
h("span", { className: "meta" },
|
|
121
|
-
"
|
|
132
|
+
"About ",
|
|
122
133
|
item.readingMinutes,
|
|
123
|
-
"
|
|
134
|
+
" min \u00B7 ",
|
|
124
135
|
item.characterCount,
|
|
125
|
-
"
|
|
136
|
+
" characters")),
|
|
126
137
|
h("h3", null, item.title),
|
|
127
|
-
h("p", { className: "excerpt" }, item.excerpt || "
|
|
138
|
+
h("p", { className: "excerpt" }, item.excerpt || "This post has no summary yet."),
|
|
128
139
|
h(Tags, { item: item })));
|
|
129
140
|
}
|
|
130
141
|
function groupByDate(items) {
|
|
@@ -168,7 +179,7 @@ function FeedDecor({ image }) {
|
|
|
168
179
|
function Feed({ items, image }) {
|
|
169
180
|
if (items.length === 0) {
|
|
170
181
|
return (h("main", { className: "feed feed-empty" },
|
|
171
|
-
h("p", { className: "empty-state" }, "
|
|
182
|
+
h("p", { className: "empty-state" }, "No public content yet."),
|
|
172
183
|
h(FeedDecor, { image: image })));
|
|
173
184
|
}
|
|
174
185
|
return (h("main", { className: "feed" },
|
|
@@ -185,15 +196,15 @@ function Feed({ items, image }) {
|
|
|
185
196
|
}
|
|
186
197
|
function Filter({ posts, thoughts }) {
|
|
187
198
|
const total = posts + thoughts;
|
|
188
|
-
return (h("div", { className: "tag-filter", "data-filter-group": "feed", role: "group", "aria-label": "
|
|
199
|
+
return (h("div", { className: "tag-filter", "data-filter-group": "feed", role: "group", "aria-label": "Filter home feed" },
|
|
189
200
|
h("button", { type: "button", "data-filter": "all", className: "active", "aria-pressed": "true" },
|
|
190
|
-
"
|
|
201
|
+
"All",
|
|
191
202
|
h("i", null, total)),
|
|
192
203
|
h("button", { type: "button", "data-filter": "post", "aria-pressed": "false" },
|
|
193
|
-
"
|
|
204
|
+
"Posts",
|
|
194
205
|
h("i", null, posts)),
|
|
195
206
|
h("button", { type: "button", "data-filter": "thought", "aria-pressed": "false" },
|
|
196
|
-
"
|
|
207
|
+
"Thoughts",
|
|
197
208
|
h("i", null, thoughts))));
|
|
198
209
|
}
|
|
199
210
|
const FILTER_SCRIPT = `(() => {
|
|
@@ -240,12 +251,12 @@ const FILTER_SCRIPT = `(() => {
|
|
|
240
251
|
}
|
|
241
252
|
}));
|
|
242
253
|
})();`;
|
|
243
|
-
export function homePage(items, theme = blogTheme, site = DEFAULT_SITE) {
|
|
254
|
+
export function homePage(items, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
244
255
|
return pageHtml({
|
|
245
256
|
site,
|
|
246
257
|
theme,
|
|
247
258
|
title: site.name,
|
|
248
|
-
styleHref:
|
|
259
|
+
styleHref: styleHref(0, stylesheet),
|
|
249
260
|
children: (h(Fragment, null,
|
|
250
261
|
h(Hero, { image: site.images?.hero ? `./${site.images.hero}` : undefined }),
|
|
251
262
|
h(Filter, { posts: items.filter((item) => item.kind === "post").length, thoughts: items.filter((item) => item.kind === "thought").length }),
|
|
@@ -253,53 +264,55 @@ export function homePage(items, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
253
264
|
h("script", { dangerouslySetInnerHTML: { __html: FILTER_SCRIPT } }))),
|
|
254
265
|
});
|
|
255
266
|
}
|
|
256
|
-
export function listPage(kind, items, theme = blogTheme, site = DEFAULT_SITE) {
|
|
267
|
+
export function listPage(kind, items, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
257
268
|
const isPost = kind === "post";
|
|
258
269
|
return pageHtml({
|
|
259
270
|
site,
|
|
260
271
|
theme,
|
|
261
|
-
title: isPost ? "
|
|
272
|
+
title: isPost ? "Posts" : "Thoughts",
|
|
262
273
|
activePath: isPost ? "/posts/" : "/thoughts/",
|
|
263
|
-
styleHref:
|
|
274
|
+
styleHref: styleHref(1, stylesheet),
|
|
264
275
|
children: (h("main", { className: "listing" },
|
|
265
276
|
h("header", { className: "listing-head" },
|
|
266
277
|
h("span", { className: "kind" }, isPost ? "Posts" : "Thoughts"),
|
|
267
|
-
h("h1", null, isPost ? "
|
|
268
|
-
h("p", null, isPost
|
|
269
|
-
|
|
278
|
+
h("h1", null, isPost ? "Posts" : "Thoughts"),
|
|
279
|
+
h("p", null, isPost
|
|
280
|
+
? "Complete arguments grown from fragments."
|
|
281
|
+
: "Short, trimmed thoughts ready to show as they are.")),
|
|
282
|
+
h("div", { className: isPost ? "post-list" : "thought-list" }, items.length === 0 ? (h("p", { className: "empty-state" }, "No public content yet.")) : (items.map((item) => isPost ? h(PostCard, { item: item }) : h(ThoughtCard, { item: item, linked: true })))))),
|
|
270
283
|
});
|
|
271
284
|
}
|
|
272
285
|
function PostNavigation({ item, posts }) {
|
|
273
286
|
const index = posts.findIndex((post) => post.slug === item.slug);
|
|
274
287
|
const older = index >= 0 ? posts[index + 1] : undefined;
|
|
275
288
|
const newer = index > 0 ? posts[index - 1] : undefined;
|
|
276
|
-
return (h("nav", { className: "post-nav", "aria-label": "
|
|
289
|
+
return (h("nav", { className: "post-nav", "aria-label": "Post navigation" },
|
|
277
290
|
older ? (h("a", { href: itemUrl(older) },
|
|
278
|
-
h("span", { className: "label" }, "\u2190
|
|
291
|
+
h("span", { className: "label" }, "\u2190 Previous post (older)"),
|
|
279
292
|
older.title)) : (h("span", null)),
|
|
280
293
|
newer ? (h("a", { className: "next", href: itemUrl(newer) },
|
|
281
|
-
h("span", { className: "label" }, "\
|
|
294
|
+
h("span", { className: "label" }, "Next post (newer) \u2192"),
|
|
282
295
|
newer.title)) : (h("span", { className: "next" },
|
|
283
|
-
h("span", { className: "label" }, "\
|
|
284
|
-
"\
|
|
296
|
+
h("span", { className: "label" }, "Next post (newer) \u2192"),
|
|
297
|
+
"Nothing newer \u2014 this is the latest post."))));
|
|
285
298
|
}
|
|
286
299
|
function RelatedThoughts({ item, thoughts, }) {
|
|
287
300
|
const related = thoughts.filter((thought) => thought.dateKey === item.dateKey).slice(0, 3);
|
|
288
301
|
if (related.length === 0)
|
|
289
302
|
return null;
|
|
290
303
|
return (h("aside", { className: "related-thoughts" },
|
|
291
|
-
h("h2", null, "
|
|
304
|
+
h("h2", null, "Thoughts from the same day"),
|
|
292
305
|
related.map((thought) => (h(ThoughtCard, { item: thought, linked: true })))));
|
|
293
306
|
}
|
|
294
|
-
export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAULT_SITE) {
|
|
295
|
-
const category = typeof item.metadata.category === "string" ? item.metadata.category : "
|
|
307
|
+
export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
308
|
+
const category = typeof item.metadata.category === "string" ? item.metadata.category : "Post";
|
|
296
309
|
return pageHtml({
|
|
297
310
|
site,
|
|
298
311
|
theme,
|
|
299
312
|
title: item.title,
|
|
300
313
|
description: item.excerpt,
|
|
301
314
|
activePath: "/posts/",
|
|
302
|
-
styleHref:
|
|
315
|
+
styleHref: styleHref(1 + slugDepth(item.slug), stylesheet),
|
|
303
316
|
children: (h("main", null,
|
|
304
317
|
h("article", null,
|
|
305
318
|
h("header", { className: "post-head" },
|
|
@@ -307,28 +320,28 @@ export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAUL
|
|
|
307
320
|
h("h1", null, item.title),
|
|
308
321
|
h("div", { className: "meta" },
|
|
309
322
|
item.dateKey,
|
|
310
|
-
" \u00B7
|
|
323
|
+
" \u00B7 about ",
|
|
311
324
|
item.readingMinutes,
|
|
312
|
-
"
|
|
325
|
+
" min \u00B7 ",
|
|
313
326
|
item.characterCount,
|
|
314
|
-
"
|
|
327
|
+
" characters")),
|
|
315
328
|
h("div", { className: "prose", dangerouslySetInnerHTML: { __html: item.html } }),
|
|
316
329
|
h(RelatedThoughts, { item: item, thoughts: thoughts }),
|
|
317
330
|
h(PostNavigation, { item: item, posts: posts })))),
|
|
318
331
|
});
|
|
319
332
|
}
|
|
320
|
-
export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE) {
|
|
333
|
+
export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
321
334
|
return pageHtml({
|
|
322
335
|
site,
|
|
323
336
|
theme,
|
|
324
337
|
title: item.title,
|
|
325
338
|
description: item.excerpt,
|
|
326
339
|
activePath: "/thoughts/",
|
|
327
|
-
styleHref:
|
|
340
|
+
styleHref: styleHref(1 + slugDepth(item.slug), stylesheet),
|
|
328
341
|
children: (h("main", null,
|
|
329
342
|
h("article", null,
|
|
330
343
|
h("header", { className: "post-head" },
|
|
331
|
-
h("span", { className: "kind" }, "
|
|
344
|
+
h("span", { className: "kind" }, "Thought"),
|
|
332
345
|
h("h1", null, item.title),
|
|
333
346
|
h("div", { className: "meta" },
|
|
334
347
|
item.dateKey,
|
|
@@ -336,21 +349,21 @@ export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
336
349
|
h("div", { className: "prose", dangerouslySetInnerHTML: { __html: item.html } })))),
|
|
337
350
|
});
|
|
338
351
|
}
|
|
339
|
-
export function aboutPage(theme = blogTheme, site = DEFAULT_SITE) {
|
|
352
|
+
export function aboutPage(theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
340
353
|
const about = site.about;
|
|
341
354
|
return pageHtml({
|
|
342
355
|
site,
|
|
343
356
|
theme,
|
|
344
|
-
title: "
|
|
357
|
+
title: "About",
|
|
345
358
|
activePath: "/about/",
|
|
346
|
-
styleHref:
|
|
359
|
+
styleHref: styleHref(1, stylesheet),
|
|
347
360
|
children: (h("main", { className: "about" },
|
|
348
361
|
h("div", { className: "avatar", "aria-hidden": "true" }, site.images?.about ? (h("img", { src: `../${site.images.about}`, alt: "", decoding: "async" })) : (h("span", { className: "avatar-initial" }, Array.from(site.name.trim())[0] ?? "M"))),
|
|
349
362
|
about ? (h(Fragment, null,
|
|
350
363
|
h("h1", null, about.heading),
|
|
351
364
|
about.paragraphs.map((paragraph) => (h("p", null, paragraph))),
|
|
352
365
|
about.links && about.links.length > 0 ? (h(Fragment, null,
|
|
353
|
-
h("h2", null, about.linksHeading ?? "
|
|
366
|
+
h("h2", null, about.linksHeading ?? "Find me"),
|
|
354
367
|
about.links.map((link) => (h("a", { className: "github-link", href: link.href, target: "_blank", rel: "me noopener noreferrer" },
|
|
355
368
|
h(DoodleGithubIcon, null),
|
|
356
369
|
h("span", { className: "github-copy" },
|
|
@@ -371,17 +384,17 @@ function SkillCard({ skill, site }) {
|
|
|
371
384
|
return (h("a", { className: `skill-card${skill.scope === "project" ? " project-skill" : ""}`, href: skillUrl(skill) },
|
|
372
385
|
h("span", { className: "skill-card-source" },
|
|
373
386
|
h("span", { className: "skill-card-path" }, skill.scope === "public" ? skillInstallCommand(site, skill) : skill.sourceBasePath),
|
|
374
|
-
skill.scope === "project" ? h("span", { className: "skill-scope-badge" }, "
|
|
387
|
+
skill.scope === "project" ? (h("span", { className: "skill-scope-badge" }, "Project Skill")) : null),
|
|
375
388
|
h("span", { className: "skill-card-main" },
|
|
376
389
|
h("strong", null, skill.name),
|
|
377
390
|
h("span", null, skill.description)),
|
|
378
391
|
h("span", { className: "skill-card-foot" },
|
|
379
392
|
h("span", null,
|
|
380
393
|
skill.files.length,
|
|
381
|
-
"
|
|
394
|
+
" Markdown files \u00B7 ",
|
|
382
395
|
skill.relations.length,
|
|
383
|
-
"
|
|
384
|
-
h("span", { className: "skill-open" }, "\
|
|
396
|
+
" references"),
|
|
397
|
+
h("span", { className: "skill-open" }, "Open directory\u00A0 \u2192"))));
|
|
385
398
|
}
|
|
386
399
|
function skillTree(files) {
|
|
387
400
|
const root = { name: "", children: [] };
|
|
@@ -410,14 +423,14 @@ function SkillTreeNodes({ nodes, activePath, }) {
|
|
|
410
423
|
h("button", { type: "button", className: node.path === activePath ? "active" : "", "data-skill-file": node.path, "aria-pressed": node.path === activePath ? "true" : "false" }, node.name))))));
|
|
411
424
|
}
|
|
412
425
|
function SkillFileTree({ skill, activePath }) {
|
|
413
|
-
return (h("nav", { className: "skill-files", "aria-label": `${skill.name}
|
|
426
|
+
return (h("nav", { className: "skill-files", "aria-label": `${skill.name} file tree` },
|
|
414
427
|
h("span", { className: "skill-files-label" }, "Files"),
|
|
415
428
|
h(SkillTreeNodes, { nodes: skillTree(skill.files), activePath: activePath })));
|
|
416
429
|
}
|
|
417
430
|
function SkillGraphNodeView({ node }) {
|
|
418
431
|
const width = node.width;
|
|
419
432
|
const height = node.height;
|
|
420
|
-
return (h("a", { className: `skill-graph-node${node.external ? " external" : ""}${node.kind ? ` ${node.kind}` : ""}`, href: node.external ? "#relations" : `#${encodeURIComponent(node.path)}`, "data-map-path": node.path, "data-map-file": node.external ? undefined : node.path, "data-map-detail": node.detail, "aria-label": `${node.name}
|
|
433
|
+
return (h("a", { className: `skill-graph-node${node.external ? " external" : ""}${node.kind ? ` ${node.kind}` : ""}`, href: node.external ? "#relations" : `#${encodeURIComponent(node.path)}`, "data-map-path": node.path, "data-map-file": node.external ? undefined : node.path, "data-map-detail": node.detail, "aria-label": `${node.name}; ${node.role}` },
|
|
421
434
|
h("rect", { x: node.x - width / 2, y: node.y - height / 2, width: width, height: height, rx: "9" }),
|
|
422
435
|
h("text", { className: "skill-graph-node-name", x: node.x, y: node.y - 4 }, node.name),
|
|
423
436
|
h("text", { className: "skill-graph-node-role", x: node.x, y: node.y + 17 }, node.role)));
|
|
@@ -425,7 +438,7 @@ function SkillGraphNodeView({ node }) {
|
|
|
425
438
|
function SkillRelationGraph({ skill, direction }) {
|
|
426
439
|
const graph = layoutSkillGraph(skill, direction);
|
|
427
440
|
const markerId = `skill-arrow-${skill.slug}-${direction.toLowerCase()}`;
|
|
428
|
-
return (h("svg", { className: `skill-graph-svg skill-graph-${direction.toLowerCase()}`, viewBox: `0 0 ${graph.width} ${graph.height}`, "aria-label": `${skill.name}
|
|
441
|
+
return (h("svg", { className: `skill-graph-svg skill-graph-${direction.toLowerCase()}`, viewBox: `0 0 ${graph.width} ${graph.height}`, "aria-label": `${skill.name} file reference graph` },
|
|
429
442
|
h("defs", null,
|
|
430
443
|
h("marker", { id: markerId, viewBox: "0 0 10 10", refX: "8", refY: "5", markerWidth: "5", markerHeight: "5", orient: "auto-start-reverse" },
|
|
431
444
|
h("path", { className: "skill-graph-arrow", d: "M 0 0 L 10 5 L 0 10 z" }))),
|
|
@@ -448,28 +461,28 @@ function SkillRelations({ skill }) {
|
|
|
448
461
|
const hasRelations = skill.relations.length > 0;
|
|
449
462
|
const compactGraph = skill.files.length + skill.externalNodes.length <= 3;
|
|
450
463
|
return (h("section", { className: "skill-relations", "data-skill-view-panel": "relations", hidden: true },
|
|
451
|
-
h("div", { className: "skill-reading-summary", "aria-label": "
|
|
464
|
+
h("div", { className: "skill-reading-summary", "aria-label": "Reading structure summary" },
|
|
452
465
|
h("span", null,
|
|
453
466
|
h("strong", null,
|
|
454
467
|
reachableFiles,
|
|
455
468
|
"/",
|
|
456
469
|
skill.files.length),
|
|
457
|
-
"
|
|
470
|
+
"reachable docs"),
|
|
458
471
|
h("span", null,
|
|
459
472
|
h("strong", null, skill.relations.length),
|
|
460
|
-
"
|
|
473
|
+
" references"),
|
|
461
474
|
h("span", null,
|
|
462
475
|
h("strong", null, maxDepth),
|
|
463
|
-
"
|
|
476
|
+
" max depth"),
|
|
464
477
|
h("span", { className: missingNodes.length > 0 ? "has-issue" : "" },
|
|
465
478
|
h("strong", null, missingNodes.length),
|
|
466
|
-
"
|
|
479
|
+
" missing targets")),
|
|
467
480
|
hasRelations ? (h(Fragment, null,
|
|
468
481
|
h("div", { className: `skill-graph-canvas${compactGraph ? " compact" : ""}` },
|
|
469
482
|
h(SkillRelationGraph, { skill: skill, direction: "LR" }),
|
|
470
483
|
h(SkillRelationGraph, { skill: skill, direction: "TB" })),
|
|
471
484
|
h("section", { className: "skill-reading-paths", "aria-labelledby": `${skill.slug}-paths` },
|
|
472
|
-
h("h3", { id: `${skill.slug}-paths` }, "
|
|
485
|
+
h("h3", { id: `${skill.slug}-paths` }, "Where references happen"),
|
|
473
486
|
h("ol", null, skill.relations.map((relation) => {
|
|
474
487
|
const sourceFile = skill.files.find((file) => file.path === relation.source);
|
|
475
488
|
return (h("li", { className: sourceFile?.reachable ? "" : "unreachable" },
|
|
@@ -479,27 +492,29 @@ function SkillRelations({ skill }) {
|
|
|
479
492
|
h("code", { title: relation.target }, relation.target)),
|
|
480
493
|
h("span", { className: "skill-reading-context" }, relation.contexts.map((context) => (h("span", null,
|
|
481
494
|
context.section,
|
|
482
|
-
context.line ? ` ·
|
|
483
|
-
context.linkText !== relation.label ? ` ·
|
|
484
|
-
!sourceFile?.reachable ? " ·
|
|
495
|
+
context.line ? ` · line ${context.line}` : "",
|
|
496
|
+
context.linkText !== relation.label ? ` · "${context.linkText}"` : "",
|
|
497
|
+
!sourceFile?.reachable ? " · entry unreachable" : ""))))));
|
|
485
498
|
}))))) : (h("div", { className: "skill-reading-empty" },
|
|
486
|
-
h("strong", null, "
|
|
499
|
+
h("strong", null, "No cross-file reading paths"),
|
|
487
500
|
h("p", null,
|
|
488
|
-
"Agent
|
|
501
|
+
"Once the Agent enters, it only reads ",
|
|
489
502
|
h("code", null, "SKILL.md"),
|
|
490
|
-
"
|
|
491
|
-
unreferencedFiles.length > 0 || missingNodes.length > 0 ? (h("aside", { className: "skill-reading-issues", "aria-label": "
|
|
492
|
-
h("strong", null, "
|
|
503
|
+
". If other reference material must be read, link it explicitly from the relevant sections with Markdown links."))),
|
|
504
|
+
unreferencedFiles.length > 0 || missingNodes.length > 0 ? (h("aside", { className: "skill-reading-issues", "aria-label": "Information structure issues" },
|
|
505
|
+
h("strong", null, "Needs attention"),
|
|
493
506
|
unreferencedFiles.length > 0 ? (h("p", null,
|
|
494
|
-
"
|
|
507
|
+
"Unreachable from the entry:",
|
|
495
508
|
unreferencedFiles.map((file) => (h("code", null, file.path))))) : null,
|
|
496
509
|
missingNodes.length > 0 ? (h("p", null,
|
|
497
|
-
"
|
|
510
|
+
"Missing link targets:",
|
|
498
511
|
missingNodes.map((node) => (h("code", null, node.path))))) : null)) : null,
|
|
499
512
|
hasRelations ? (h("div", { className: "skill-graph-status", "data-map-status": true, "aria-live": "polite" },
|
|
500
|
-
"
|
|
501
|
-
externalNodes.length > 0
|
|
502
|
-
|
|
513
|
+
"Select a node in the graph to see its role in the reading chain",
|
|
514
|
+
externalNodes.length > 0
|
|
515
|
+
? `; ${externalNodes.length} targets outside the Skill currently`
|
|
516
|
+
: "",
|
|
517
|
+
".")) : null));
|
|
503
518
|
}
|
|
504
519
|
const SKILL_BROWSER_SCRIPT = `(() => {
|
|
505
520
|
document.querySelectorAll('[data-skill-browser]').forEach((browser) => {
|
|
@@ -585,42 +600,42 @@ const SKILL_BROWSER_SCRIPT = `(() => {
|
|
|
585
600
|
const command = copyButton.dataset.copyCommand;
|
|
586
601
|
if (!command) return;
|
|
587
602
|
await navigator.clipboard.writeText(command);
|
|
588
|
-
copyButton.textContent = '
|
|
589
|
-
window.setTimeout(() => { copyButton.textContent = '
|
|
603
|
+
copyButton.textContent = 'Copied';
|
|
604
|
+
window.setTimeout(() => { copyButton.textContent = 'Copy'; }, 1400);
|
|
590
605
|
});
|
|
591
606
|
});
|
|
592
607
|
})();`;
|
|
593
|
-
export function skillsIndexPage(skills, theme = blogTheme, site = DEFAULT_SITE) {
|
|
608
|
+
export function skillsIndexPage(skills, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
594
609
|
const publicSkills = skills.filter((skill) => skill.scope === "public");
|
|
595
610
|
const projectSkills = skills.filter((skill) => skill.scope === "project");
|
|
596
611
|
return pageHtml({
|
|
597
612
|
site,
|
|
598
613
|
theme,
|
|
599
614
|
title: "Skills",
|
|
600
|
-
description: "
|
|
615
|
+
description: "Methods and working conventions that can be handed to an Agent.",
|
|
601
616
|
activePath: "/skills/",
|
|
602
|
-
styleHref:
|
|
617
|
+
styleHref: styleHref(1, stylesheet),
|
|
603
618
|
children: (h("main", { className: "skills-index" },
|
|
604
619
|
h("header", { className: "skills-head" },
|
|
605
620
|
h("span", { className: "skill-path" }, "~/skills"),
|
|
606
621
|
h("h1", null, "Skills"),
|
|
607
|
-
h("p", null, "
|
|
622
|
+
h("p", null, "Methods shared publicly and usable directly by an Agent.")),
|
|
608
623
|
h("section", { className: "skill-group", "aria-labelledby": "public-skills-heading" },
|
|
609
624
|
h("header", { className: "skill-group-head" },
|
|
610
|
-
h("h2", { id: "public-skills-heading" }, "
|
|
611
|
-
h("span", null, "
|
|
612
|
-
publicSkills.length > 0 ? (h("div", { className: "skill-list" }, publicSkills.map((skill) => (h(SkillCard, { skill: skill, site: site }))))) : (h("p", { className: "skill-group-empty" }, "
|
|
625
|
+
h("h2", { id: "public-skills-heading" }, "Public Skills"),
|
|
626
|
+
h("span", null, "Public content")),
|
|
627
|
+
publicSkills.length > 0 ? (h("div", { className: "skill-list" }, publicSkills.map((skill) => (h(SkillCard, { skill: skill, site: site }))))) : (h("p", { className: "skill-group-empty" }, "Public Skills have no content yet."))),
|
|
613
628
|
projectSkills.length > 0 ? (h("section", { className: "skill-group project-skills", "aria-labelledby": "project-skills-heading" },
|
|
614
629
|
h("header", { className: "skill-group-head" },
|
|
615
630
|
h("div", null,
|
|
616
|
-
h("h2", { id: "project-skills-heading" }, "
|
|
617
|
-
h("small", null, "
|
|
631
|
+
h("h2", { id: "project-skills-heading" }, "Project Skills"),
|
|
632
|
+
h("small", null, "Development and content workflows used inside this project")),
|
|
618
633
|
h("span", null, ".agents/skills/")),
|
|
619
634
|
h("div", { className: "skill-list" }, projectSkills.map((skill) => (h(SkillCard, { skill: skill, site: site })))))) : null,
|
|
620
|
-
h("p", { className: "skills-prototype-note" }, "
|
|
635
|
+
h("p", { className: "skills-prototype-note" }, "The reference graph starts from SKILL.md and is generated at build time from Markdown links."))),
|
|
621
636
|
});
|
|
622
637
|
}
|
|
623
|
-
export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE) {
|
|
638
|
+
export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
624
639
|
const firstFile = skill.files[0];
|
|
625
640
|
const command = skill.scope === "public" ? skillInstallCommand(site, skill) : skill.sourceBasePath;
|
|
626
641
|
return pageHtml({
|
|
@@ -629,20 +644,22 @@ export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
629
644
|
title: skill.name,
|
|
630
645
|
description: skill.description,
|
|
631
646
|
activePath: "/skills/",
|
|
632
|
-
styleHref: skill.scope === "public"
|
|
647
|
+
styleHref: skill.scope === "public"
|
|
648
|
+
? styleHref(1 + slugDepth(skill.slug), stylesheet)
|
|
649
|
+
: styleHref(2 + slugDepth(skill.slug), stylesheet),
|
|
633
650
|
children: (h("main", { className: "skill-page" },
|
|
634
|
-
h("a", { className: "skill-back", href: "/skills/" }, "\u2190
|
|
651
|
+
h("a", { className: "skill-back", href: "/skills/" }, "\u2190 All Skills"),
|
|
635
652
|
h("header", { className: "skill-browser-head" },
|
|
636
653
|
h("span", { className: "skill-command" },
|
|
637
|
-
skill.scope === "project" ? (h("span", { className: "skill-scope-badge" }, "
|
|
654
|
+
skill.scope === "project" ? (h("span", { className: "skill-scope-badge" }, "Project Skill \u00B7 Internal")) : null,
|
|
638
655
|
h("code", null, command)),
|
|
639
656
|
h("span", { className: "skill-browser-actions" },
|
|
640
|
-
h("button", { type: "button", "data-copy-command": command }, skill.scope === "public" ? "
|
|
657
|
+
h("button", { type: "button", "data-copy-command": command }, skill.scope === "public" ? "Copy" : "Copy path"),
|
|
641
658
|
site.repositorySourceUrl ? (h("a", { href: `${site.repositorySourceUrl}/${skill.sourceBasePath}`, target: "_blank", rel: "noopener noreferrer" }, "GitHub \u2197")) : null)),
|
|
642
659
|
h("div", { className: "skill-stage", "data-skill-browser": true },
|
|
643
|
-
h("nav", { className: "skill-view-tabs", "aria-label": "Skill
|
|
644
|
-
h("button", { type: "button", "data-skill-view": "files", "aria-pressed": "true" }, "
|
|
645
|
-
h("button", { type: "button", "data-skill-view": "relations", "aria-pressed": "false" }, "
|
|
660
|
+
h("nav", { className: "skill-view-tabs", "aria-label": "Skill view mode" },
|
|
661
|
+
h("button", { type: "button", "data-skill-view": "files", "aria-pressed": "true" }, "File browser"),
|
|
662
|
+
h("button", { type: "button", "data-skill-view": "relations", "aria-pressed": "false" }, "Reference graph")),
|
|
646
663
|
h("div", { className: "skill-browser", "data-skill-view-panel": "files" },
|
|
647
664
|
h(SkillFileTree, { skill: skill, activePath: firstFile.path }),
|
|
648
665
|
h("section", { className: "skill-viewer", "aria-live": "polite" },
|
|
@@ -656,64 +673,85 @@ export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
656
673
|
h("script", { dangerouslySetInnerHTML: { __html: SKILL_BROWSER_SCRIPT } }))),
|
|
657
674
|
});
|
|
658
675
|
}
|
|
659
|
-
export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE) {
|
|
676
|
+
export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE, pages) {
|
|
677
|
+
const siteMap = pages
|
|
678
|
+
? pages.map((page) => `- [${page.title}](${sitePageUrl(page.path)})`)
|
|
679
|
+
: [
|
|
680
|
+
"- [Home](/)",
|
|
681
|
+
"- [Posts](/posts/)",
|
|
682
|
+
"- [Thoughts](/thoughts/)",
|
|
683
|
+
"- [Skills](/skills/) (browse files and the auto-generated Markdown reference graph)",
|
|
684
|
+
"- [About](/about/)",
|
|
685
|
+
];
|
|
660
686
|
const lines = [
|
|
661
687
|
`# ${site.name}`,
|
|
662
688
|
"",
|
|
663
|
-
">
|
|
689
|
+
"> Site index and content conventions for Agents to read.",
|
|
664
690
|
"",
|
|
665
691
|
"## Site map",
|
|
666
692
|
"",
|
|
667
|
-
|
|
668
|
-
"- [文章](/posts/)",
|
|
669
|
-
"- [想法](/thoughts/)",
|
|
670
|
-
"- [Skills](/skills/)(可浏览文件与自动生成的 Markdown 引用关系)",
|
|
671
|
-
"- [关于](/about/)",
|
|
693
|
+
...siteMap,
|
|
672
694
|
"",
|
|
673
695
|
"## Content model",
|
|
674
696
|
"",
|
|
675
|
-
`- \`${site.contentDirectory}/thoughts/\`
|
|
676
|
-
`- \`${site.contentDirectory}/posts/\`
|
|
677
|
-
"-
|
|
678
|
-
"-
|
|
679
|
-
"",
|
|
680
|
-
"## Public content",
|
|
681
|
-
"",
|
|
682
|
-
`公开文章:${posts.length} 篇。`,
|
|
683
|
-
`公开想法:${thoughts.length} 条。`,
|
|
684
|
-
"",
|
|
685
|
-
"### Posts",
|
|
686
|
-
"",
|
|
687
|
-
...posts.map((post) => `- [${post.title}](${itemUrl(post)}) — ${post.dateKey}`),
|
|
688
|
-
"",
|
|
689
|
-
"### Thoughts",
|
|
690
|
-
"",
|
|
691
|
-
...thoughts.map((thought) => `- [${thought.title}](${itemUrl(thought)}) — ${thought.dateKey}`),
|
|
697
|
+
`- \`${site.contentDirectory}/thoughts/\` stores trimmed, directly presentable short thoughts.`,
|
|
698
|
+
`- \`${site.contentDirectory}/posts/\` stores full posts; files whose name or path segment starts with \`_\` are drafts and are not published or rendered.`,
|
|
699
|
+
"- Content files support `.md` and `.mdx`, and may start with YAML frontmatter.",
|
|
700
|
+
"- Frontmatter currently has no required fields; `title`, `date`, `created_at`, `category` and `tags` are recognized by the site.",
|
|
692
701
|
"",
|
|
702
|
+
// A consumer that owns the site map owns the routes of its content too.
|
|
703
|
+
...(pages
|
|
704
|
+
? []
|
|
705
|
+
: [
|
|
706
|
+
"## Public content",
|
|
707
|
+
"",
|
|
708
|
+
`Public posts: ${posts.length}.`,
|
|
709
|
+
`Public thoughts: ${thoughts.length}.`,
|
|
710
|
+
"",
|
|
711
|
+
"### Posts",
|
|
712
|
+
"",
|
|
713
|
+
...posts.map((post) => `- [${post.title}](${itemUrl(post)}) — ${post.dateKey}`),
|
|
714
|
+
"",
|
|
715
|
+
"### Thoughts",
|
|
716
|
+
"",
|
|
717
|
+
...thoughts.map((thought) => `- [${thought.title}](${itemUrl(thought)}) — ${thought.dateKey}`),
|
|
718
|
+
"",
|
|
719
|
+
]),
|
|
693
720
|
];
|
|
694
721
|
return lines.join("\n");
|
|
695
722
|
}
|
|
696
|
-
export function llmText(posts, thoughts, site = DEFAULT_SITE) {
|
|
723
|
+
export function llmText(posts, thoughts, site = DEFAULT_SITE, pages) {
|
|
724
|
+
const routes = pages
|
|
725
|
+
? pages.map((page) => `- ${page.title}: ${sitePageUrl(page.path)}`)
|
|
726
|
+
: [
|
|
727
|
+
"- Home: /",
|
|
728
|
+
"- Posts: /posts/",
|
|
729
|
+
"- Thoughts: /thoughts/",
|
|
730
|
+
"- Skills: /skills/",
|
|
731
|
+
"- About: /about/",
|
|
732
|
+
];
|
|
697
733
|
return [
|
|
698
734
|
`# ${site.name}`,
|
|
699
735
|
`> ${site.description}`,
|
|
700
736
|
"",
|
|
701
737
|
"## Pages",
|
|
702
|
-
|
|
703
|
-
"- 文章: /posts/",
|
|
704
|
-
"- 想法: /thoughts/",
|
|
705
|
-
"- Skills: /skills/",
|
|
706
|
-
"- 关于: /about/",
|
|
738
|
+
...routes,
|
|
707
739
|
"- Agent Markdown: /agents.md",
|
|
708
740
|
"",
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
741
|
+
// A consumer that owns the site map owns the routes of its content too;
|
|
742
|
+
// listing the content under routes the site does not publish would be wrong.
|
|
743
|
+
...(pages
|
|
744
|
+
? []
|
|
745
|
+
: [
|
|
746
|
+
"## Posts",
|
|
747
|
+
...posts.map((post) => `- ${post.title}: ${itemUrl(post)}`),
|
|
748
|
+
"",
|
|
749
|
+
"## Thoughts",
|
|
750
|
+
...thoughts.map((thought) => `- ${thought.title}: ${itemUrl(thought)}`),
|
|
751
|
+
"",
|
|
752
|
+
]),
|
|
715
753
|
"## Rules",
|
|
716
|
-
"-
|
|
717
|
-
"-
|
|
754
|
+
"- Only the public build output is considered presentable content.",
|
|
755
|
+
"- Drafts whose names start with _ under posts are not published or rendered.",
|
|
718
756
|
].join("\n");
|
|
719
757
|
}
|
package/dist/src/skills.js
CHANGED
|
@@ -44,7 +44,7 @@ function markdownLinks(source) {
|
|
|
44
44
|
}
|
|
45
45
|
if (url) {
|
|
46
46
|
links.push({
|
|
47
|
-
label: nodeText(node).replaceAll(/\s+/g, " ").trim() || "
|
|
47
|
+
label: nodeText(node).replaceAll(/\s+/g, " ").trim() || "Open",
|
|
48
48
|
url,
|
|
49
49
|
section,
|
|
50
50
|
line: node.position?.start?.line,
|
|
@@ -53,7 +53,7 @@ function markdownLinks(source) {
|
|
|
53
53
|
}
|
|
54
54
|
node.children?.forEach((child) => collectLinks(child, section));
|
|
55
55
|
};
|
|
56
|
-
let section = "
|
|
56
|
+
let section = "Beginning of document";
|
|
57
57
|
for (const node of tree.children ?? []) {
|
|
58
58
|
if (node.type === "heading") {
|
|
59
59
|
section = nodeText(node).replaceAll(/\s+/g, " ").trim() || section;
|
|
@@ -79,7 +79,7 @@ function inferredDescription(body) {
|
|
|
79
79
|
.split(/\r?\n\s*\r?\n/)
|
|
80
80
|
.map(plainText)
|
|
81
81
|
.find(Boolean);
|
|
82
|
-
return (paragraph ?? "
|
|
82
|
+
return (paragraph ?? "A skill that can be handed to an Agent.").slice(0, 180);
|
|
83
83
|
}
|
|
84
84
|
async function collectFiles(directory) {
|
|
85
85
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
@@ -107,16 +107,16 @@ function isExternalUrl(url) {
|
|
|
107
107
|
function externalDetail(url) {
|
|
108
108
|
try {
|
|
109
109
|
const parsed = new URL(url);
|
|
110
|
-
return `Agent
|
|
110
|
+
return `The Agent leaves the Skill and continues to ${parsed.hostname}.`;
|
|
111
111
|
}
|
|
112
112
|
catch {
|
|
113
|
-
return "
|
|
113
|
+
return "Points to input or material outside the Skill directory.";
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
function relationLabel(label, target) {
|
|
117
117
|
const targetName = target.endsWith("/") ? target : (target.split("/").at(-1) ?? target);
|
|
118
118
|
if (label === target || label === targetName)
|
|
119
|
-
return "
|
|
119
|
+
return "Reference";
|
|
120
120
|
return label.length > 16 ? `${label.slice(0, 15)}…` : label;
|
|
121
121
|
}
|
|
122
122
|
function compactGraphLabel(label) {
|
|
@@ -209,7 +209,7 @@ async function loadSkill(directory, sourceBasePath, scope) {
|
|
|
209
209
|
target = link.url;
|
|
210
210
|
externalNodes.set(target, {
|
|
211
211
|
path: target,
|
|
212
|
-
role: "
|
|
212
|
+
role: "External link",
|
|
213
213
|
detail: externalDetail(target),
|
|
214
214
|
kind: "external",
|
|
215
215
|
});
|
|
@@ -223,15 +223,15 @@ async function loadSkill(directory, sourceBasePath, scope) {
|
|
|
223
223
|
externalNodes.set(target, {
|
|
224
224
|
path: target,
|
|
225
225
|
role: resolved.kind === "asset"
|
|
226
|
-
? "
|
|
226
|
+
? "Local asset"
|
|
227
227
|
: resolved.kind === "outside"
|
|
228
|
-
? "Skill
|
|
229
|
-
: "
|
|
228
|
+
? "Outside the Skill"
|
|
229
|
+
: "Missing reference",
|
|
230
230
|
detail: resolved.kind === "asset"
|
|
231
|
-
? "Markdown
|
|
231
|
+
? "The Markdown explicitly links to this non-text asset."
|
|
232
232
|
: resolved.kind === "outside"
|
|
233
|
-
? "
|
|
234
|
-
: "Markdown
|
|
233
|
+
? "The link points outside the Skill directory; the Agent leaves the current Skill and continues reading."
|
|
234
|
+
: "The link exists in the Markdown, but there is no corresponding file in the Skill directory.",
|
|
235
235
|
kind: resolved.kind === "outside" ? "external" : resolved.kind,
|
|
236
236
|
});
|
|
237
237
|
}
|
|
@@ -253,7 +253,7 @@ async function loadSkill(directory, sourceBasePath, scope) {
|
|
|
253
253
|
return {
|
|
254
254
|
source,
|
|
255
255
|
target,
|
|
256
|
-
label: compactGraphLabel(sections[0] === "
|
|
256
|
+
label: compactGraphLabel(sections[0] === "Beginning of document" ? labels.join(" / ") : sections.join(" / ")),
|
|
257
257
|
contexts,
|
|
258
258
|
};
|
|
259
259
|
});
|
|
@@ -272,12 +272,12 @@ async function loadSkill(directory, sourceBasePath, scope) {
|
|
|
272
272
|
const reachable = depth !== undefined;
|
|
273
273
|
return {
|
|
274
274
|
path,
|
|
275
|
-
role: path === "SKILL.md" ? "
|
|
275
|
+
role: path === "SKILL.md" ? "Entry" : reachable ? `${depth}-level reference` : "Not referenced",
|
|
276
276
|
detail: path === "SKILL.md"
|
|
277
|
-
? "Agent
|
|
277
|
+
? "The Agent starts reading this Skill here."
|
|
278
278
|
: reachable
|
|
279
|
-
?
|
|
280
|
-
: "
|
|
279
|
+
? `Reachable from SKILL.md through ${depth} links.`
|
|
280
|
+
: "Not on the link chain from SKILL.md, so the Agent will not naturally read this.",
|
|
281
281
|
content: documents.get(path).body,
|
|
282
282
|
reachable,
|
|
283
283
|
depth,
|
package/dist/src/theme.js
CHANGED
|
@@ -71,7 +71,7 @@ const styles = css `
|
|
|
71
71
|
text-rendering: optimizeLegibility;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/*
|
|
74
|
+
/* Top bar: keep the order, but stop looking like a masthead. */
|
|
75
75
|
header.site {
|
|
76
76
|
display: flex;
|
|
77
77
|
align-items: center;
|
|
@@ -255,7 +255,7 @@ const styles = css `
|
|
|
255
255
|
pointer-events: none;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
/*
|
|
258
|
+
/* Home feed: dates are breathing points, not a mechanical timeline. */
|
|
259
259
|
.feed {
|
|
260
260
|
position: relative;
|
|
261
261
|
padding: 8px 0 48px;
|
|
@@ -385,7 +385,7 @@ const styles = css `
|
|
|
385
385
|
letter-spacing: 0.06em;
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
/*
|
|
388
|
+
/* Both content kinds look like paper on a desk, but keep their own weight. */
|
|
389
389
|
.post-card,
|
|
390
390
|
.thought {
|
|
391
391
|
background-color: ${blogTokens.card};
|
|
@@ -548,7 +548,7 @@ const styles = css `
|
|
|
548
548
|
outline: none;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
/*
|
|
551
|
+
/* Filters are lightweight controls, not publication legends. */
|
|
552
552
|
.tag-filter {
|
|
553
553
|
display: flex;
|
|
554
554
|
flex-wrap: wrap;
|
|
@@ -598,7 +598,7 @@ const styles = css `
|
|
|
598
598
|
transform: translateY(1px);
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
-
/*
|
|
601
|
+
/* Post and thought detail. */
|
|
602
602
|
.post-head {
|
|
603
603
|
padding: 68px 0 37px;
|
|
604
604
|
border-bottom: 1px solid ${blogTokens.line};
|
|
@@ -892,7 +892,7 @@ const styles = css `
|
|
|
892
892
|
text-align: right;
|
|
893
893
|
}
|
|
894
894
|
|
|
895
|
-
/*
|
|
895
|
+
/* Index and about pages. */
|
|
896
896
|
.listing,
|
|
897
897
|
.about,
|
|
898
898
|
.skills-index,
|
|
@@ -1009,7 +1009,7 @@ const styles = css `
|
|
|
1009
1009
|
letter-spacing: 0.06em;
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
|
-
/* Skills
|
|
1012
|
+
/* Skills: separate content by catalog and document semantics instead of displaying it as a product. */
|
|
1013
1013
|
.skills-head {
|
|
1014
1014
|
margin-bottom: 33px;
|
|
1015
1015
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meowpress",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.0",
|
|
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.
|
|
41
|
-
"meowjsx": "^0.
|
|
40
|
+
"meowcss": "^0.2.0-alpha.0",
|
|
41
|
+
"meowjsx": "^0.2.0-alpha.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"meowjsx": "0.
|
|
45
|
-
"meowcss": "0.
|
|
44
|
+
"meowjsx": "0.2.0-alpha.0",
|
|
45
|
+
"meowcss": "0.2.0-alpha.0"
|
|
46
46
|
}
|
|
47
47
|
}
|