Skip to content

Commit 571ca61

Browse files
authored
fix: repair .zip download and serve raw SKILL.md without attribution comment (#241)
* fix: generate SKILL.zip for every skill, not just multi-file ones The skill detail page shows an unconditional 'Download .zip' link for every skill (introduced in #169), but the zip endpoint only generated a file for multi-file skills. Single-file skills therefore linked to a zip that was never built, returning 404. Drop the multi-file guard so a zip is generated for every skill: single-file skills get a zip containing just SKILL.md; multi-file skills continue to bundle SKILL.md plus their references/ tree. * fix: stop prepending attribution comment to raw SKILL.md The /skills/<name>/SKILL.md endpoint prepended an HTML comment with source/origin/publisher/license/updated metadata (added in #171). This made the served markdown non-identical to the repo file and cluttered the top of the document. Serve the raw SKILL.md bytes unchanged. Provenance is still carried in the X-Content-Source and X-License response headers, so nothing is lost for machine consumers.
1 parent 6165991 commit 571ca61

2 files changed

Lines changed: 11 additions & 15 deletions

File tree

src/pages/.well-known/skills/[name]/SKILL.zip.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Serves /.well-known/skills/<name>/SKILL.zip at build time
2-
// Only generated for multi-file skills (SKILL.md + references)
2+
// Generated for every skill so the download link always resolves. Single-file
3+
// skills produce a zip containing just SKILL.md; multi-file skills bundle
4+
// SKILL.md plus their references/ tree.
35
import type { APIRoute } from 'astro';
46
import archiver from 'archiver';
57
import { readdirSync, readFileSync, statSync } from 'fs';
@@ -58,7 +60,7 @@ async function buildZip(skillDir: string): Promise<Buffer> {
5860

5961
let _cache: SkillZipData[] | null = null;
6062

61-
async function loadMultiFileSkillZips(): Promise<SkillZipData[]> {
63+
async function loadSkillZips(): Promise<SkillZipData[]> {
6264
if (_cache) return _cache;
6365

6466
const dirs = readdirSync(SKILLS_DIR)
@@ -76,9 +78,6 @@ async function loadMultiFileSkillZips(): Promise<SkillZipData[]> {
7678

7779
for (const dir of dirs) {
7880
const skillDir = join(SKILLS_DIR, dir);
79-
const files = collectFilePaths(skillDir, skillDir);
80-
if (files.length <= 1) continue; // skip single-file skills
81-
8281
const content = readFileSync(join(skillDir, 'SKILL.md'), 'utf-8');
8382
const name = parseName(content);
8483
if (!name) continue;
@@ -92,7 +91,7 @@ async function loadMultiFileSkillZips(): Promise<SkillZipData[]> {
9291
}
9392

9493
export async function getStaticPaths() {
95-
const skills = await loadMultiFileSkillZips();
94+
const skills = await loadSkillZips();
9695
return skills.map((s) => ({
9796
params: { name: s.name },
9897
props: { zipBuffer: s.zipBuffer },

src/pages/skills/[slug]/SKILL.md.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Raw markdown endpoint: /skills/{slug}/SKILL.md returns the unmodified SKILL.md
2-
// bytes (frontmatter + body). Served with text/markdown so LLM crawlers can
3-
// parse it natively. An attribution header line is prepended as a markdown
4-
// comment so the origin stays visible even if the file is copy-pasted.
2+
// bytes (frontmatter + body), byte-identical to the repo file. Served with
3+
// text/markdown so LLM crawlers can parse it natively. Provenance is carried in
4+
// the X-Content-Source and X-License response headers, not in the body.
55

66
import type { APIRoute } from 'astro';
77
import { getAllSkills, getSkillGitInfo, getSkillRawMarkdown, githubCommitUrl } from '../../../lib/skills';
8-
import { SITE, absUrl } from '../../../lib/site';
8+
import { SITE } from '../../../lib/site';
99

1010
export async function getStaticPaths() {
1111
const skills = await getAllSkills();
@@ -19,13 +19,10 @@ export const GET: APIRoute = async ({ props }) => {
1919
if (!skill) return new Response('Not found', { status: 404 });
2020

2121
const body = await getSkillRawMarkdown(skill);
22-
const { sha, updatedAt } = await getSkillGitInfo(skill);
22+
const { sha } = await getSkillGitInfo(skill);
2323
const sourceUrl = githubCommitUrl(slug, sha);
24-
const attribution =
25-
`<!-- source: ${absUrl(`/skills/${slug}/`)} | origin: ${sourceUrl} | ` +
26-
`publisher: ${SITE.author.name} | license: ${SITE.license.spdx} | updated: ${updatedAt} -->\n`;
2724

28-
return new Response(attribution + body, {
25+
return new Response(body, {
2926
headers: {
3027
'Content-Type': 'text/markdown; charset=utf-8',
3128
'X-Content-Source': sourceUrl,

0 commit comments

Comments
 (0)