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-skills.yml b/.github/workflows/sync-skills.yml new file mode 100644 index 0000000..4e9d5b9 --- /dev/null +++ b/.github/workflows/sync-skills.yml @@ -0,0 +1,30 @@ +name: Sync Skills + +on: + push: + branches: [main] + paths: + - 'skills/**' + +jobs: + sync: + runs-on: ubuntu-slim + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: true + + - name: Sync skills + run: node .github/scripts/sync-skills.js + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v7 + with: + 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/.github/workflows/validate-skill.yml b/.github/workflows/validate-skill.yml new file mode 100644 index 0000000..35e31b9 --- /dev/null +++ b/.github/workflows/validate-skill.yml @@ -0,0 +1,65 @@ +name: Validate Agent Skill + +on: + push: + branches: [main] + paths: + - "**/SKILL.md" + pull_request: + branches: [main] + paths: + - "**/SKILL.md" + +concurrency: + group: validate-skill-${{ github.ref }} + cancel-in-progress: true + +jobs: + detect-changes: + runs-on: ubuntu-slim + if: github.event.pull_request.draft != true && github.actor != 'dependabot[bot]' + outputs: + skills: ${{ steps.changed-skills.outputs.skills }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Get changed skills + id: changed-skills + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + BASE=${{ github.event.pull_request.base.sha }} + HEAD=${{ github.event.pull_request.head.sha }} + else + BASE=${{ github.event.before }} + HEAD=${{ github.event.after }} + fi + + # Find changed SKILL.md files and extract skill directories + SKILLS=$(git diff --name-only $BASE $HEAD | \ + grep 'SKILL.md$' | \ + xargs -I {} dirname {} | \ + sort -u | \ + jq -R -s -c 'split("\n") | map(select(length > 0))') + + echo "skills=$SKILLS" >> $GITHUB_OUTPUT + echo "Changed skills: $SKILLS" + + validate: + needs: detect-changes + if: needs.detect-changes.outputs.skills != '[]' + runs-on: ubuntu-slim + strategy: + fail-fast: false + matrix: + skill: ${{ fromJson(needs.detect-changes.outputs.skills) }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Validate ${{ matrix.skill }} + uses: Flash-Brew-Digital/validate-skill@v1 + with: + path: ${{ matrix.skill }} \ No newline at end of file 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