<\/div>/i;
if (!fallbackHtml || !rootPattern.test(html)) {
return html;
}
return html.replace(rootPattern, `
${fallbackHtml}
`);
}
function buildHomeMeta({ catalogCount, imageUrl, canonicalUrl }) {
const visibleCount = Math.max(catalogCount, HOME_CATALOG_COUNT_FALLBACK);
const formattedCount = visibleCount.toLocaleString('en-US');
const title = `AAS Core Preview | Agent-first stacks backed by ${formattedCount}+ skills`;
const description = `Use AAS Core preview to discover, recommend, validate, and plan explainable skill stacks for Codex, Claude Code, and compatible clients, backed by ${formattedCount}+ cataloged skills.`;
const catalogBaseUrl = canonicalUrl.replace(/\/$/, '');
const sourceCodeEntity = {
'@context': 'https://schema.org',
'@type': 'SoftwareSourceCode',
name: SITE_NAME,
description: `AAS Core preview is a local agent-first control plane for recommending, validating, and planning exact skill stacks backed by ${formattedCount}+ agentic skills.`,
url: REPOSITORY_URL,
sameAs: [...new Set([
canonicalUrl,
HOSTED_CATALOG_URL,
'https://www.npmjs.com/package/agentic-awesome-skills',
])],
mainEntityOfPage: canonicalUrl,
codeRepository: REPOSITORY_URL,
applicationCategory: 'DeveloperApplication',
keywords: [
'AI coding assistant skills',
'Claude Code skills',
'Codex CLI skills',
'Cursor skills',
'Gemini CLI skills',
'Antigravity skills',
'Antigravity CLI skills',
'GitHub AI skills repository',
'AI agent skills GitHub',
'AAS Core',
'skill recommendation',
'agent stack',
'Model Context Protocol',
'specialized plugins',
'SKILL.md',
],
isAccessibleForFree: true,
programmingLanguage: {
'@type': 'ComputerLanguage',
name: 'Markdown',
url: 'https://en.wikipedia.org/wiki/Markdown',
},
license: `${REPOSITORY_URL}/blob/main/LICENSE`,
};
return {
title,
description,
canonicalUrl,
ogTitle: title,
ogDescription: description,
ogImage: imageUrl,
twitterCard: 'summary_large_image',
jsonLd: [
{
'@context': 'https://schema.org',
'@type': 'CollectionPage',
name: SITE_NAME,
description,
url: canonicalUrl,
sameAs: REPOSITORY_URL,
about: sourceCodeEntity,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
},
mainEntity: {
'@type': 'ItemList',
name: `${SITE_NAME} catalog`,
},
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${REPOSITORY_URL}#organization`,
name: SITE_NAME,
url: REPOSITORY_URL,
sameAs: [
'https://x.com/AASkills_',
'https://www.npmjs.com/package/agentic-awesome-skills',
HOSTED_CATALOG_URL,
],
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
inLanguage: 'en',
potentialAction: {
'@type': 'SearchAction',
target: `${catalogBaseUrl}/?q={search_term_string}`,
'query-input': 'required name=search_term_string',
},
},
sourceCodeEntity,
{
'@context': 'https://schema.org',
'@type': 'FAQPage',
url: canonicalUrl,
mainEntity: buildFaqItems(`${formattedCount}+`).map((item) => ({
'@type': 'Question',
name: item.question,
acceptedAnswer: {
'@type': 'Answer',
text: item.answer,
},
})),
},
],
};
}
function buildSkillMeta({ skill, isPriority, imageUrl, canonicalUrl }) {
const safeName = safeText(skill.name) || 'Unnamed skill';
const safeDescription = safeText(skill.description) || 'Installable AI skill';
const safeCategory = safeText(skill.category) || 'Tools';
const safeSource = safeText(skill.source) || 'community contributors';
const added = skill.date_added ? `Added ${skill.date_added}. ` : '';
const trust = isPriority ? ' Prioritized in our catalog for quality and reuse. ' : ' ';
const title = `${safeName} | ${SITE_NAME}`;
const description = `${added}Use the @${safeName} skill for ${safeDescription} (${safeCategory}, ${safeSource}).${trust}Install and run quickly with your CLI workflow.`;
return {
title,
description,
canonicalUrl,
ogTitle: `@${safeName} | ${SITE_NAME}`,
ogDescription: description,
ogImage: imageUrl,
twitterCard: 'summary',
jsonLd: {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
'@id': canonicalUrl,
name: `@${safeName}`,
applicationCategory: safeCategory,
description,
url: canonicalUrl,
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
availability: 'https://schema.org/InStock',
},
provider: {
'@type': 'Organization',
name: SITE_NAME,
},
keywords: [safeCategory, safeSource],
inLanguage: 'en',
operatingSystem: 'Cross-platform',
isPartOf: {
'@type': 'CollectionPage',
name: SITE_NAME,
},
},
};
}
function buildPluginsMeta({ pluginCount, imageUrl, canonicalUrl }) {
const countLabel = pluginCount > 0 ? `${pluginCount.toLocaleString('en-US')} ` : '';
const description = `Compare ${countLabel}specialized plugin packs for web apps, security, data analytics, documents, DevOps, QA, OSS maintenance, mobile apps, automation, and agent or MCP systems.`;
return {
title: `AAS Specialized Plugins | ${countLabel}AI coding workflow packs`,
description,
canonicalUrl,
ogTitle: 'AAS Specialized Plugins | AI coding workflow packs',
ogDescription: description,
ogImage: imageUrl,
twitterCard: 'summary_large_image',
jsonLd: [
{
'@context': 'https://schema.org',
'@type': 'CollectionPage',
name: 'AAS Specialized Plugins',
description,
url: canonicalUrl,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: canonicalUrl.replace(/\/plugins\/?$/, ''),
sameAs: REPOSITORY_URL,
},
mainEntity: {
'@type': 'ItemList',
name: 'AAS specialized plugin packs',
numberOfItems: pluginCount,
},
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${REPOSITORY_URL}#organization`,
name: SITE_NAME,
url: REPOSITORY_URL,
},
],
};
}
function buildWorkbenchMeta({ imageUrl, canonicalUrl }) {
const title = 'AAS Core Stack Review | Agentic Awesome Skills';
const description = 'Review an AAS Core stack manifest and immutable preview plan locally in your browser. Imports stay in memory and cannot install or apply changes.';
const catalogBaseUrl = canonicalUrl.replace(/\/workbench\/?$/, '');
const catalogRootUrl = `${catalogBaseUrl}/`;
const sourceCodeEntity = {
'@context': 'https://schema.org',
'@type': 'SoftwareSourceCode',
name: SITE_NAME,
description: 'Canonical source for AAS Core, its local MCP and CLI, and the supporting skills catalog and registry evidence.',
url: REPOSITORY_URL,
sameAs: [canonicalUrl, catalogRootUrl, 'https://www.npmjs.com/package/agentic-awesome-skills'],
mainEntityOfPage: canonicalUrl,
codeRepository: REPOSITORY_URL,
applicationCategory: 'DeveloperApplication',
isAccessibleForFree: true,
license: `${REPOSITORY_URL}/blob/main/LICENSE`,
};
return {
title,
description,
canonicalUrl,
ogTitle: title,
ogDescription: description,
ogImage: imageUrl,
twitterCard: 'summary_large_image',
jsonLd: [
{
'@context': 'https://schema.org',
'@type': 'WebPage',
name: 'Agentic Awesome Skills Workbench',
headline: 'Review what AAS Core recommended',
description,
url: canonicalUrl,
mainEntityOfPage: canonicalUrl,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
},
about: sourceCodeEntity,
},
{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: SITE_NAME, item: catalogRootUrl },
{ '@type': 'ListItem', position: 2, name: 'Skill Workbench', item: canonicalUrl },
],
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${REPOSITORY_URL}#organization`,
name: SITE_NAME,
url: REPOSITORY_URL,
sameAs: [
'https://x.com/AASkills_',
'https://www.npmjs.com/package/agentic-awesome-skills',
catalogRootUrl,
],
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
inLanguage: 'en',
potentialAction: {
'@type': 'SearchAction',
target: `${catalogBaseUrl}/?q={search_term_string}`,
'query-input': 'required name=search_term_string',
},
},
sourceCodeEntity,
],
};
}
function buildTopicLandingMeta({ page, featuredSkills = [], imageUrl, canonicalUrl }) {
const catalogBaseUrl = canonicalUrl.replace(/\/topics\/[^/]+\/?$/, '');
const keywords = Array.isArray(page.keywords) ? page.keywords.join(', ') : '';
const sourceCodeEntity = {
'@context': 'https://schema.org',
'@type': 'SoftwareSourceCode',
name: SITE_NAME,
description: 'AAS Core preview is a local agent-first control plane backed by an evidence-rich catalog of agentic skills.',
url: REPOSITORY_URL,
sameAs: [...new Set([
canonicalUrl,
HOSTED_CATALOG_URL,
'https://www.npmjs.com/package/agentic-awesome-skills',
])],
mainEntityOfPage: canonicalUrl,
codeRepository: REPOSITORY_URL,
applicationCategory: 'DeveloperApplication',
keywords: [
...(Array.isArray(page.keywords) ? page.keywords : []),
'specialized plugins',
'SKILL.md',
],
isAccessibleForFree: true,
programmingLanguage: {
'@type': 'ComputerLanguage',
name: 'Markdown',
url: 'https://en.wikipedia.org/wiki/Markdown',
},
license: `${REPOSITORY_URL}/blob/main/LICENSE`,
};
return {
title: page.title,
description: page.description,
canonicalUrl,
ogTitle: page.title,
ogDescription: page.description,
ogImage: imageUrl,
twitterCard: 'summary_large_image',
jsonLd: [
{
'@context': 'https://schema.org',
'@type': 'WebPage',
name: page.h1,
headline: page.h1,
description: page.description,
url: canonicalUrl,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
},
about: sourceCodeEntity,
keywords,
mainEntity: {
'@type': 'ItemList',
name: `${page.eyebrow} recommended skills`,
numberOfItems: featuredSkills.length || (Array.isArray(page.sections) ? page.sections.length : 0),
itemListElement: featuredSkills.length > 0
? featuredSkills.map((skill, index) => ({
'@type': 'ListItem',
position: index + 1,
name: safeText(skill.name) || safeText(skill.id),
description: safeText(skill.description),
url: routeToUrl(`/skill/${encodeURIComponent(skill.id)}`, catalogBaseUrl),
}))
: (Array.isArray(page.sections) ? page.sections.map((section, index) => ({
'@type': 'ListItem',
position: index + 1,
name: section.heading,
description: section.body,
})) : []),
},
},
{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: SITE_NAME,
item: HOSTED_CATALOG_URL,
},
{
'@type': 'ListItem',
position: 2,
name: page.h1,
item: canonicalUrl,
},
],
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${REPOSITORY_URL}#organization`,
name: SITE_NAME,
url: REPOSITORY_URL,
sameAs: [
'https://x.com/AASkills_',
'https://www.npmjs.com/package/agentic-awesome-skills',
HOSTED_CATALOG_URL,
],
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
inLanguage: 'en',
potentialAction: {
'@type': 'SearchAction',
target: `${catalogBaseUrl}/?q={search_term_string}`,
'query-input': 'required name=search_term_string',
},
},
sourceCodeEntity,
],
};
}
function applySeoMeta(templateHtml, meta) {
let output = templateHtml;
const title = safeText(meta.title);
const description = safeText(meta.description);
const canonical = safeText(meta.canonicalUrl);
const ogTitle = safeText(meta.ogTitle || title);
const ogDescription = safeText(meta.ogDescription || description);
const ogImage = safeText(meta.ogImage);
output = setTitleTag(output, title);
output = setMetaTag(output, 'name', 'description', description);
output = setMetaTag(output, 'property', 'og:type', 'website');
output = setMetaTag(output, 'property', 'og:title', ogTitle);
output = setMetaTag(output, 'property', 'og:description', ogDescription);
output = setMetaTag(output, 'property', 'og:site_name', SITE_NAME);
output = setMetaTag(output, 'property', 'og:url', canonical);
output = setMetaTag(output, 'name', 'twitter:card', safeText(meta.twitterCard || 'summary'));
output = setMetaTag(output, 'name', 'twitter:title', ogTitle);
output = setMetaTag(output, 'name', 'twitter:description', ogDescription);
output = setMetaTag(output, 'name', 'twitter:image:alt', `${ogTitle} preview`);
output = setMetaTag(output, 'property', 'og:image', ogImage);
output = setMetaTag(output, 'name', 'twitter:image', ogImage);
output = setLinkTag(output, 'canonical', canonical);
output = setJsonLdTag(output, meta.jsonLd);
return output;
}
function writePrerenderedRoute(routePath, templateHtml, meta, fallbackHtml = '') {
const filePath = routeToFilePath(routePath);
const rendered = setRootFallback(applySeoMeta(templateHtml, meta), fallbackHtml);
const directory = path.dirname(filePath);
ensureDirectory(directory);
fs.writeFileSync(filePath, rendered, 'utf-8');
}
function readCatalog() {
if (!fs.existsSync(SKILLS_PATH)) {
throw new Error(`Skills catalog not found at ${SKILLS_PATH}`);
}
const raw = fs.readFileSync(SKILLS_PATH, 'utf-8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) {
throw new Error('Skills catalog must be an array.');
}
return parsed;
}
function readSeoLandingPages() {
if (!fs.existsSync(SEO_LANDING_PAGES_PATH)) {
return [];
}
const raw = fs.readFileSync(SEO_LANDING_PAGES_PATH, 'utf-8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) {
throw new Error('SEO landing pages data must be an array.');
}
return parsed;
}
function main() {
if (!fs.existsSync(TEMPLATE_PATH)) {
throw new Error(`Built index file not found at ${TEMPLATE_PATH}. Run npm run build before prerender.`);
}
const template = fs.readFileSync(TEMPLATE_PATH, 'utf-8');
const skills = readCatalog();
const landingPages = readSeoLandingPages();
const siteBaseUrl = getSiteBaseUrl();
const topCount = parseCount(process.env.PRERENDER_TOP_SKILL_COUNT || process.env.TOP_SKILL_COUNT, 180);
const topSkillPaths = selectTopSkillEntries(skills, topCount);
const skillMap = new Map(skills.map((skill) => [skill.id, skill]));
const topSkillSet = new Set(topSkillPaths.map((routePath) => routePath.replace(/^\/skill\//, '')));
const socialImage = `${siteBaseUrl.replace(/\/+$/, '')}/${PRERENDER_SOCIAL_IMAGE}`;
const homeCanonical = routeToUrl('/', siteBaseUrl);
const homeMeta = buildHomeMeta({
catalogCount: skills.length,
imageUrl: socialImage,
canonicalUrl: homeCanonical,
});
writePrerenderedRoute('/', template, homeMeta, buildHomeFallback({ landingPages, siteBaseUrl }));
const pluginsCanonical = routeToUrl('/plugins', siteBaseUrl);
const pluginsMeta = buildPluginsMeta({
pluginCount: 15,
imageUrl: socialImage,
canonicalUrl: pluginsCanonical,
});
writePrerenderedRoute('/plugins', template, pluginsMeta);
const workbenchCanonical = routeToUrl('/workbench', siteBaseUrl);
const workbenchMeta = buildWorkbenchMeta({
imageUrl: socialImage,
canonicalUrl: workbenchCanonical,
});
writePrerenderedRoute('/workbench', template, workbenchMeta);
for (const page of landingPages) {
if (!page?.slug) {
continue;
}
const routePath = `/topics/${encodeURIComponent(page.slug)}`;
const canonicalUrl = routeToUrl(routePath, siteBaseUrl);
const landingMeta = buildTopicLandingMeta({
page,
featuredSkills: getCuratedSkillsForLandingPage(page, skills),
imageUrl: socialImage,
canonicalUrl,
});
writePrerenderedRoute(
routePath,
template,
landingMeta,
buildTopicFallback({ page, landingPages, skills, siteBaseUrl }),
);
}
const allSkillPaths = skills
.filter((skill) => skill && skill.id)
.map((skill) => `/skill/${encodeURIComponent(skill.id)}`)
.sort();
for (const skillRoute of allSkillPaths) {
const decodedId = decodeURIComponent(skillRoute.replace(/^\/skill\//, ''));
const skill = skillMap.get(decodedId);
if (!skill) {
continue;
}
const canonicalUrl = routeToUrl(skillRoute, siteBaseUrl);
const skillMeta = buildSkillMeta({
skill,
isPriority: topSkillSet.has(encodeURIComponent(decodedId)),
imageUrl: socialImage,
canonicalUrl,
});
writePrerenderedRoute(
skillRoute,
template,
skillMeta,
buildSkillFallback({ skill, landingPages, siteBaseUrl }),
);
}
}
main();