#!/usr/bin/env node
/**
* One-shot bootstrap: upgrade existing page-template HTML + add 7 premium templates.
* Run: node scripts/bootstrap-premium-page-templates.mjs
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const skillsRoot = path.join(root, 'skills');
const previewRoot = path.join(root, 'public/assets/template-previews');
const FOOTER =
'';
function writeSkillFile(skillName, relativePath, content) {
const full = path.join(skillsRoot, skillName, relativePath);
fs.mkdirSync(path.dirname(full), { recursive: true });
fs.writeFileSync(full, `${content.trim()}\n`, 'utf8');
}
function writePreview(name, svg) {
fs.writeFileSync(path.join(previewRoot, name), `${svg.trim()}\n`, 'utf8');
}
const upgraded = {
'page-template-travel/templates/travel-hero.html': `
{{PAGE_TITLE}}
{{HERO_EYEBROW}}
{{HERO_HEADLINE}}
{{HERO_SUBTITLE}}
{{MAIN_CONTENT_HTML}}
${FOOTER}
`,
'page-template-campaign/templates/campaign-cta.html': `
{{PAGE_TITLE}}
{{PRODUCT_NAME}}
{{HERO_HEADLINE}}
{{HERO_SUBTITLE}}
{{CTA_LABEL}}
${FOOTER}
`,
'page-template-survey/templates/survey-basic.html': `
{{PAGE_TITLE}}
{{SURVEY_HEADLINE}}
{{SURVEY_INTRO_HTML}}
${FOOTER}
`,
'page-template-restaurant/templates/restaurant-menu.html': `
{{PAGE_TITLE}}
{{VENUE_TAG}}
{{VENUE_NAME}}
{{VENUE_SUBTITLE}}
{{MENU_SECTIONS_HTML}}
${FOOTER}
`,
'page-template-portfolio/templates/portfolio-grid.html': `
{{PAGE_TITLE}}
{{CREATOR_NAME}}
{{CREATOR_TAGLINE}}
{{PROJECT_CARDS_HTML}}
${FOOTER}
`,
};
for (const [rel, html] of Object.entries(upgraded)) {
const [skill, ...rest] = rel.split('/');
writeSkillFile(skill, rest.join('/'), html);
}
console.log(`Upgraded ${Object.keys(upgraded).length} templates (batch 1/3)`);
const upgraded2 = {
'page-template-event/templates/event-invite.html': `
{{PAGE_TITLE}}
{{EVENT_DATE}} · {{EVENT_CITY}}
{{EVENT_TITLE}}
{{EVENT_INTRO}}
{{AGENDA_HTML}}
${FOOTER}
`,
'page-template-course/templates/course-landing.html': `
{{PAGE_TITLE}}
{{SYLLABUS_HTML}}
${FOOTER}
`,
'page-template-realestate/templates/realestate-listing.html': `
{{PAGE_TITLE}}
{{LISTING_TITLE}}
{{LISTING_LOCATION}}
{{HIGHLIGHTS_HTML}}
${FOOTER}
`,
'page-template-blog/templates/blog-article.html': `
{{PAGE_TITLE}}
{{ARTICLE_DATE}} · {{ARTICLE_AUTHOR}}
{{ARTICLE_TITLE}}
{{ARTICLE_BODY_HTML}}
${FOOTER}
`,
'page-template-resume/templates/resume-profile.html': `
{{PAGE_TITLE}}
{{EXPERIENCE_HTML}}
{{SKILLS_HTML}}
${FOOTER}
`,
'page-template-wedding/templates/wedding-invite.html': `
{{PAGE_TITLE}}
诚挚邀请
{{COUPLE_NAMES}}
{{WEDDING_DATE}}
{{WEDDING_VENUE}}
{{DETAILS_HTML}}
${FOOTER}
`,
'page-template-fitness/templates/fitness-club.html': `
{{PAGE_TITLE}}
{{SCHEDULE_HTML}}
${FOOTER}
`,
'page-template-product-grid/templates/product-grid.html': `
{{PAGE_TITLE}}
{{COLLECTION_TITLE}}
{{COLLECTION_SUBTITLE}}
{{PRODUCT_GRID_HTML}}
${FOOTER}
`,
};
for (const [rel, html] of Object.entries(upgraded2)) {
const [skill, ...rest] = rel.split('/');
writeSkillFile(skill, rest.join('/'), html);
}
console.log(`Upgraded ${Object.keys(upgraded2).length} templates (batch 2/3)`);
const newTemplates = {
'page-template-saas': {
templateFile: 'saas-landing.html',
label: 'SaaS 产品页',
description: '渐变 Hero + 功能特性 + 定价区,适合 B2B 软件落地页',
sortOrder: 140,
priceCents: 1290,
previewSvg: 'saas-landing',
html: `
{{PAGE_TITLE}}
{{PRODUCT_NAME}}
{{HERO_HEADLINE}}
{{HERO_SUBTITLE}}
{{CTA_LABEL}}
${FOOTER}
`,
},
'page-template-hotel': {
templateFile: 'hotel-retreat.html',
label: '酒店民宿',
description: '大图 Hero + 设施亮点 + 房型卡片,适合精品酒店与民宿',
sortOrder: 150,
priceCents: 1190,
previewSvg: 'hotel-retreat',
html: `
{{PAGE_TITLE}}
{{HOTEL_NAME}}
{{HOTEL_TAGLINE}}
{{AMENITIES_HTML}}
{{ROOMS_HTML}}
{{BOOKING_CTA_LABEL}}
${FOOTER}
`,
},
'page-template-agency': {
templateFile: 'agency-studio.html',
label: '创意机构',
description: '极简大标题 + 服务列表 + 案例展示,适合设计/广告工作室',
sortOrder: 160,
priceCents: 1090,
previewSvg: 'agency-studio',
html: `
{{PAGE_TITLE}}
{{AGENCY_NAME}}
{{AGENCY_TAGLINE}}
{{SERVICES_HTML}}
{{SHOWCASE_HTML}}
{{CONTACT_HTML}}
${FOOTER}
`,
},
'page-template-photography': {
templateFile: 'photo-gallery.html',
label: '摄影画廊',
description: '全屏 Hero + 瀑布流作品区,适合摄影师与视觉艺术家',
sortOrder: 170,
priceCents: 990,
previewSvg: 'photo-gallery',
html: `
{{PAGE_TITLE}}
{{PHOTOGRAPHER_NAME}}
{{PHOTOGRAPHER_BIO}}
{{GALLERY_HTML}}
${FOOTER}
`,
},
'page-template-podcast': {
templateFile: 'podcast-show.html',
label: '播客节目',
description: '封面 Hero + 单集列表 + 订阅引导,适合播客与音频栏目',
sortOrder: 180,
priceCents: 890,
previewSvg: 'podcast-show',
html: `
{{PAGE_TITLE}}
{{SHOW_NAME}}
{{SHOW_TAGLINE}}
{{EPISODES_HTML}}
{{SUBSCRIBE_HTML}}
${FOOTER}
`,
},
'page-template-charity': {
templateFile: 'charity-cause.html',
label: '公益募捐',
description: '情感 Hero + 项目故事 + 捐赠 CTA,适合公益与 CSR 活动',
sortOrder: 190,
priceCents: 0,
previewSvg: 'charity-cause',
html: `
{{PAGE_TITLE}}
{{CAUSE_TITLE}}
{{CAUSE_SUBTITLE}}
{{IMPACT_HTML}}
{{STORY_HTML}}
{{DONATE_CTA_LABEL}}
${FOOTER}
`,
},
'page-template-startup': {
templateFile: 'startup-pitch.html',
label: '创业融资',
description: 'Pitch Deck 风格单页:愿景 + 数据指标 + 团队 + CTA',
sortOrder: 200,
priceCents: 1490,
previewSvg: 'startup-pitch',
html: `
{{PAGE_TITLE}}
{{STARTUP_NAME}}
{{PITCH_HEADLINE}}
{{PITCH_SUBTITLE}}
{{CTA_LABEL}}
{{METRICS_HTML}}
{{VISION_HTML}}
{{TEAM_HTML}}
${FOOTER}
`,
},
};
function skillYaml(name, meta) {
return `name: ${name}
version: 1.0.0
label: ${meta.label}
description: ${meta.description}
catalog:
label: ${meta.label}
description: ${meta.description}
previewImage: /assets/template-previews/${meta.previewSvg}.svg
priceCents: ${meta.priceCents}
billingMode: ${meta.priceCents > 0 ? 'one_time' : 'free'}
sortOrder: ${meta.sortOrder}
trigger:
keywords:
- ${meta.label}
router:
promptKey: ${name}
priority: 25
executors:
- goose
`;
}
function skillMd(name, meta, templateFile) {
return `---
name: ${name}
description: ${meta.description}
---
# ${meta.label}
1. load_skill → \`${name}\`
2. read_file → \`.agents/skills/${name}/templates/${templateFile}\`
3. 替换全部占位符后 write_file → \`public/*.html\`
4. 遵守 static-page-publish 回复 Markdown 链接
`;
}
function previewSvg(title, subtitle, color1, color2) {
return ``;
}
const previewColors = {
'saas-landing': ['#312e81', '#0f172a'],
'hotel-retreat': ['#78350f', '#1c1917'],
'agency-studio': ['#f5f5f5', '#e5e5e5'],
'photo-gallery': ['#171717', '#0a0a0a'],
'podcast-show': ['#4c1d95', '#0f0f14'],
'charity-cause': ['#fef3c7', '#fde68a'],
'startup-pitch': ['#0c4a6e', '#030712'],
};
for (const [skillName, meta] of Object.entries(newTemplates)) {
writeSkillFile(skillName, `templates/${meta.templateFile}`, meta.html);
writeSkillFile(skillName, 'skill.yaml', skillYaml(skillName, meta));
writeSkillFile(skillName, 'SKILL.md', skillMd(skillName, meta, meta.templateFile));
const [c1, c2] = previewColors[meta.previewSvg];
writePreview(`${meta.previewSvg}.svg`, previewSvg(meta.label, 'Premium Template', c1, c2));
}
console.log(`Created ${Object.keys(newTemplates).length} new template skills (batch 3/3)`);
console.log('Done. Update chat-skills.mjs, skills-registry.mjs, page-template-catalog.mjs demos, and test count.');