#!/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}}
{{PRODUCT_NAME}}
{{SELLING_POINTS_HTML}}
{{OFFER_HTML}}
{{CTA_LABEL}}
${FOOTER} `, 'page-template-survey/templates/survey-basic.html': ` {{PAGE_TITLE}}

{{SURVEY_HEADLINE}}

{{SURVEY_INTRO_HTML}}
{{FORM_FIELDS_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}}

{{COURSE_TITLE}}

{{COURSE_SUBTITLE}}

{{CTA_LABEL}}
{{SYLLABUS_HTML}}
${FOOTER} `, 'page-template-realestate/templates/realestate-listing.html': ` {{PAGE_TITLE}}

{{LISTING_TITLE}}

{{LISTING_LOCATION}}

{{SPECS_TABLE_HTML}}
{{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}}

{{FULL_NAME}}

{{JOB_TITLE}}

{{SUMMARY}}

{{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}}

{{CLUB_NAME}}

{{CLUB_SLOGAN}}

{{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}}
{{PRODUCT_NAME}}
{{FEATURES_HTML}}
{{PRICING_HTML}}
${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}}

${FOOTER} `, }, 'page-template-podcast': { templateFile: 'podcast-show.html', label: '播客节目', description: '封面 Hero + 单集列表 + 订阅引导,适合播客与音频栏目', sortOrder: 180, priceCents: 890, previewSvg: 'podcast-show', html: ` {{PAGE_TITLE}}
{{SHOW_NAME}}

{{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}}
${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 ` ${subtitle} ${title} `; } 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.');