diff --git a/.github/scripts/sync-marketplace.js b/.github/scripts/sync-marketplace.js deleted file mode 100644 index 26b3444..0000000 --- a/.github/scripts/sync-marketplace.js +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env node -/** - * Sync marketplace.json with skills directory. - * - * Scans the skills/ directory for valid skills (directories containing SKILL.md) - * and updates marketplace.json to match. - */ - -const fs = require("fs"); -const path = require("path"); - -const SKILLS_DIR = "skills"; -const MARKETPLACE_FILE = ".claude-plugin/marketplace.json"; - -function getSkillsFromDirectory() { - if (!fs.existsSync(SKILLS_DIR)) { - return []; - } - - return fs - .readdirSync(SKILLS_DIR, { withFileTypes: true }) - .filter((entry) => { - if (!entry.isDirectory()) return false; - const skillFile = path.join(SKILLS_DIR, entry.name, "SKILL.md"); - return fs.existsSync(skillFile); - }) - .map((entry) => `./${SKILLS_DIR}/${entry.name}`) - .sort(); -} - -function updateSkillCount(description, count) { - return description.replace(/\d+ marketing skills/, `${count} marketing skills`); -} - -function main() { - const currentSkills = getSkillsFromDirectory(); - - const marketplace = JSON.parse(fs.readFileSync(MARKETPLACE_FILE, "utf8")); - const plugin = marketplace.plugins[0]; - const existingSkills = plugin.skills || []; - - // Check if update needed - if (JSON.stringify(currentSkills) === JSON.stringify(existingSkills)) { - console.log("marketplace.json is already in sync"); - return; - } - - // Update skills list - plugin.skills = currentSkills; - - // Update description with new count - plugin.description = updateSkillCount(plugin.description, currentSkills.length); - - // Write updated marketplace.json - fs.writeFileSync(MARKETPLACE_FILE, JSON.stringify(marketplace, null, 2) + "\n"); - - // Report changes - const added = currentSkills.filter((s) => !existingSkills.includes(s)); - const removed = existingSkills.filter((s) => !currentSkills.includes(s)); - - if (added.length) console.log(`Added: ${added.join(", ")}`); - if (removed.length) console.log(`Removed: ${removed.join(", ")}`); - - console.log(`Updated marketplace.json (${currentSkills.length} skills)`); -} - -main(); diff --git a/.github/scripts/sync-skills.js b/.github/scripts/sync-skills.js new file mode 100644 index 0000000..0e7e0cd --- /dev/null +++ b/.github/scripts/sync-skills.js @@ -0,0 +1,184 @@ +#!/usr/bin/env node +/** + * Sync marketplace.json and README.md with skills directory. + * + * Scans the skills/ directory for valid skills (directories containing SKILL.md) + * and updates marketplace.json and the README skills table to match. + */ + +const fs = require("fs"); +const path = require("path"); + +const SKILLS_DIR = "skills"; +const MARKETPLACE_FILE = ".claude-plugin/marketplace.json"; +const README_FILE = "README.md"; + +/** + * Parse YAML frontmatter from a SKILL.md file + */ +function parseFrontmatter(content) { + const match = content.match(/^---\n([\s\S]*?)\n---/); + if (!match) return {}; + + const frontmatter = {}; + const lines = match[1].split("\n"); + + for (const line of lines) { + const colonIndex = line.indexOf(":"); + if (colonIndex === -1) continue; + + const key = line.slice(0, colonIndex).trim(); + let value = line.slice(colonIndex + 1).trim(); + + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + + frontmatter[key] = value; + } + + return frontmatter; +} + +/** + * Get all skills with their metadata + */ +function getSkillsWithMetadata() { + if (!fs.existsSync(SKILLS_DIR)) { + return []; + } + + return fs + .readdirSync(SKILLS_DIR, { withFileTypes: true }) + .filter((entry) => { + if (!entry.isDirectory()) return false; + const skillFile = path.join(SKILLS_DIR, entry.name, "SKILL.md"); + return fs.existsSync(skillFile); + }) + .map((entry) => { + const skillFile = path.join(SKILLS_DIR, entry.name, "SKILL.md"); + const content = fs.readFileSync(skillFile, "utf8"); + const frontmatter = parseFrontmatter(content); + + return { + dir: entry.name, + path: `./${SKILLS_DIR}/${entry.name}`, + name: frontmatter.name || entry.name, + description: frontmatter.description || "", + }; + }) + .sort((a, b) => a.name.localeCompare(b.name)); +} + +/** + * Update skill count in description + */ +function updateSkillCount(description, count) { + return description.replace(/\d+ marketing skills/, `${count} marketing skills`); +} + +/** + * Truncate description to a maximum length + */ +function truncateDescription(description, maxLength = 120) { + if (description.length <= maxLength) return description; + + // Find last space before maxLength to avoid cutting words + const truncated = description.slice(0, maxLength); + const lastSpace = truncated.lastIndexOf(" "); + + return truncated.slice(0, lastSpace) + "..."; +} + +/** + * Generate the skills table for README + */ +function generateSkillsTable(skills) { + const header = "| Skill | Description |\n|-------|-------------|"; + const rows = skills.map((skill) => { + const link = `[${skill.name}](skills/${skill.dir}/)`; + const description = truncateDescription(skill.description); + return `| ${link} | ${description} |`; + }); + + return [header, ...rows].join("\n"); +} + +/** + * Update README.md with new skills table + */ +function updateReadme(skills) { + const content = fs.readFileSync(README_FILE, "utf8"); + + // Match content between skill list markers + const tableRegex = /(\n)[\s\S]*?(\n)/; + const newTable = generateSkillsTable(skills); + + if (!tableRegex.test(content)) { + console.log("WARNING: Could not find skill markers in README.md"); + return false; + } + + const newContent = content.replace(tableRegex, `$1${newTable}$2`); + + if (newContent === content) { + return false; + } + + fs.writeFileSync(README_FILE, newContent); + return true; +} + +/** + * Update marketplace.json with skills list + */ +function updateMarketplace(skills) { + const marketplace = JSON.parse(fs.readFileSync(MARKETPLACE_FILE, "utf8")); + const plugin = marketplace.plugins[0]; + const existingSkills = plugin.skills || []; + const currentSkills = skills.map((s) => s.path); + + if (JSON.stringify(currentSkills) === JSON.stringify(existingSkills)) { + return { updated: false }; + } + + plugin.skills = currentSkills; + plugin.description = updateSkillCount(plugin.description, currentSkills.length); + + fs.writeFileSync(MARKETPLACE_FILE, JSON.stringify(marketplace, null, 2) + "\n"); + + const added = currentSkills.filter((s) => !existingSkills.includes(s)); + const removed = existingSkills.filter((s) => !currentSkills.includes(s)); + + return { updated: true, added, removed }; +} + +function main() { + const skills = getSkillsWithMetadata(); + + const marketplaceResult = updateMarketplace(skills); + const readmeUpdated = updateReadme(skills); + + if (!marketplaceResult.updated && !readmeUpdated) { + console.log("Everything is already in sync"); + return; + } + + if (marketplaceResult.updated) { + if (marketplaceResult.added.length) { + console.log(`Added: ${marketplaceResult.added.join(", ")}`); + } + if (marketplaceResult.removed.length) { + console.log(`Removed: ${marketplaceResult.removed.join(", ")}`); + } + console.log(`Updated marketplace.json (${skills.length} skills)`); + } + + if (readmeUpdated) { + console.log("Updated README.md skills table"); + } +} + +main(); diff --git a/.github/workflows/sync-claude-plugin.yml b/.github/workflows/sync-skills.yml similarity index 50% rename from .github/workflows/sync-claude-plugin.yml rename to .github/workflows/sync-skills.yml index 5af4098..4e9d5b9 100644 --- a/.github/workflows/sync-claude-plugin.yml +++ b/.github/workflows/sync-skills.yml @@ -1,4 +1,4 @@ -name: Sync Claude Plugin +name: Sync Skills on: push: @@ -18,11 +18,13 @@ jobs: with: persist-credentials: true - - name: Sync skills to marketplace.json - run: node .github/scripts/sync-marketplace.js + - name: Sync skills + run: node .github/scripts/sync-skills.js - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v7 with: - commit_message: "chore: sync marketplace.json with skills directory" - file_pattern: .claude-plugin/marketplace.json + commit_user_name: Coreybot + commit_user_email: coreybot+github-actions[bot]@users.noreply.github.com + commit_message: "chore: sync skills with marketplace.json and README" + file_pattern: ".claude-plugin/marketplace.json README.md" diff --git a/README.md b/README.md index d12375f..da04d22 100644 --- a/README.md +++ b/README.md @@ -14,31 +14,33 @@ Skills are markdown files that give AI agents specialized knowledge and workflow ## Available Skills -| Skill | Description | Triggers | -|-------|-------------|----------| -| [ab-test-setup](skills/ab-test-setup/) | Plan and implement A/B tests | "A/B test," "split test," "experiment" | -| [analytics-tracking](skills/analytics-tracking/) | Set up tracking and measurement | "tracking," "GA4," "GTM," "events" | -| [competitor-alternatives](skills/competitor-alternatives/) | Competitor comparison and alternative pages | "vs page," "alternative page," "[X] vs [Y]" | -| [copy-editing](skills/copy-editing/) | Edit and polish existing copy | "edit this copy," "review my copy," "copy sweep" | -| [copywriting](skills/copywriting/) | Write or improve marketing copy | "write copy," "rewrite," "headlines," "CTA copy" | -| [email-sequence](skills/email-sequence/) | Build email sequences and drip campaigns | "email sequence," "drip campaign," "nurture" | -| [form-cro](skills/form-cro/) | Optimize lead capture and contact forms | "form optimization," "lead form," "contact form" | -| [free-tool-strategy](skills/free-tool-strategy/) | Plan engineering-as-marketing tools | "free tool," "calculator," "lead gen tool" | -| [launch-strategy](skills/launch-strategy/) | Product launches and feature announcements | "launch," "Product Hunt," "feature release" | -| [marketing-ideas](skills/marketing-ideas/) | 140 SaaS marketing ideas and strategies | "marketing ideas," "growth ideas," "how to market" | -| [marketing-psychology](skills/marketing-psychology/) | 70+ mental models for marketing | "psychology," "mental models," "cognitive bias" | -| [onboarding-cro](skills/onboarding-cro/) | Improve user activation and onboarding | "onboarding," "activation," "first-run experience" | -| [page-cro](skills/page-cro/) | Conversion optimization for any marketing page | "optimize [page]," "CRO," "page isn't converting" | -| [paid-ads](skills/paid-ads/) | Create and optimize paid ad campaigns | "PPC," "Google Ads," "Meta ads," "paid media" | -| [paywall-upgrade-cro](skills/paywall-upgrade-cro/) | In-app paywalls and upgrade screens | "paywall," "upgrade screen," "feature gate" | -| [popup-cro](skills/popup-cro/) | Create/optimize popups and modals | "popup," "modal," "exit intent" | -| [pricing-strategy](skills/pricing-strategy/) | Design pricing, packaging, and monetization | "pricing," "tiers," "freemium," "willingness to pay" | -| [programmatic-seo](skills/programmatic-seo/) | Build SEO pages at scale | "programmatic SEO," "template pages," "pages at scale" | -| [referral-program](skills/referral-program/) | Design referral and affiliate programs | "referral," "affiliate," "word of mouth," "viral" | -| [schema-markup](skills/schema-markup/) | Add structured data and rich snippets | "schema," "JSON-LD," "structured data" | -| [seo-audit](skills/seo-audit/) | Audit technical and on-page SEO | "SEO audit," "technical SEO," "not ranking" | -| [signup-flow-cro](skills/signup-flow-cro/) | Optimize signup and registration flows | "signup optimization," "registration form" | -| [social-content](skills/social-content/) | Create and schedule social media content | "LinkedIn post," "Twitter thread," "social media" | + +| Skill | Description | +|-------|-------------| +| [ab-test-setup](skills/ab-test-setup/) | When the user wants to plan, design, or implement an A/B test or experiment. Also use when the user mentions "A/B... | +| [analytics-tracking](skills/analytics-tracking/) | When the user wants to set up, improve, or audit analytics tracking and measurement. Also use when the user mentions... | +| [competitor-alternatives](skills/competitor-alternatives/) | When the user wants to create competitor comparison or alternative pages for SEO and sales enablement. Also use when... | +| [copy-editing](skills/copy-editing/) | When the user wants to edit, review, or improve existing marketing copy. Also use when the user mentions 'edit this... | +| [copywriting](skills/copywriting/) | When the user wants to write, rewrite, or improve marketing copy for any page — including homepage, landing pages,... | +| [email-sequence](skills/email-sequence/) | When the user wants to create or optimize an email sequence, drip campaign, automated email flow, or lifecycle email... | +| [form-cro](skills/form-cro/) | When the user wants to optimize any form that is NOT signup/registration — including lead capture forms, contact forms,... | +| [free-tool-strategy](skills/free-tool-strategy/) | When the user wants to plan, evaluate, or build a free tool for marketing purposes — lead generation, SEO value, or... | +| [launch-strategy](skills/launch-strategy/) | When the user wants to plan a product launch, feature announcement, or release strategy. Also use when the user... | +| [marketing-ideas](skills/marketing-ideas/) | When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the... | +| [marketing-psychology](skills/marketing-psychology/) | When the user wants to apply psychological principles, mental models, or behavioral science to marketing. Also use when... | +| [onboarding-cro](skills/onboarding-cro/) | When the user wants to optimize post-signup onboarding, user activation, first-run experience, or time-to-value. Also... | +| [page-cro](skills/page-cro/) | When the user wants to optimize, improve, or increase conversions on any marketing page — including homepage, landing... | +| [paid-ads](skills/paid-ads/) | When the user wants help with paid advertising campaigns on Google Ads, Meta (Facebook/Instagram), LinkedIn, Twitter/X,... | +| [paywall-upgrade-cro](skills/paywall-upgrade-cro/) | When the user wants to create or optimize in-app paywalls, upgrade screens, upsell modals, or feature gates. Also use... | +| [popup-cro](skills/popup-cro/) | When the user wants to create or optimize popups, modals, overlays, slide-ins, or banners for conversion purposes. Also... | +| [pricing-strategy](skills/pricing-strategy/) | When the user wants help with pricing decisions, packaging, or monetization strategy. Also use when the user mentions... | +| [programmatic-seo](skills/programmatic-seo/) | When the user wants to create SEO-driven pages at scale using templates and data. Also use when the user mentions... | +| [referral-program](skills/referral-program/) | When the user wants to create, optimize, or analyze a referral program, affiliate program, or word-of-mouth strategy.... | +| [schema-markup](skills/schema-markup/) | When the user wants to add, fix, or optimize schema markup and structured data on their site. Also use when the user... | +| [seo-audit](skills/seo-audit/) | When the user wants to audit, review, or diagnose SEO issues on their site. Also use when the user mentions "SEO... | +| [signup-flow-cro](skills/signup-flow-cro/) | When the user wants to optimize signup, registration, account creation, or trial activation flows. Also use when the... | +| [social-content](skills/social-content/) | When the user wants help creating, scheduling, or optimizing social media content for LinkedIn, Twitter/X, Instagram,... | + ## Installation