Initial release v1.0.0
Open-source marketplace for AI Agent skills. Features: - Next.js 15 web app with i18n (en/fa) - CLI tool for skill installation (npx skillhub) - GitHub crawler/indexer with multi-strategy discovery - Security scanning for all indexed skills - Self-hostable with Docker Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
26
scripts/add-new-categories.sql
Normal file
26
scripts/add-new-categories.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- SkillHub: Add 7 new categories
|
||||
-- Safe to run - only adds new categories, doesn't affect existing links
|
||||
-- Run: docker exec -i skillhub-db psql -U postgres -d skillhub < scripts/add-new-categories.sql
|
||||
|
||||
-- Insert 7 new categories (ignore if already exist)
|
||||
INSERT INTO categories (id, name, slug, description, icon, color, sort_order, skill_count) VALUES
|
||||
('cat-productivity', 'Productivity & Notes', 'productivity-notes', 'Note-taking, reminders, task management, calendar, and personal productivity tools', 'StickyNote', '#84CC16', 16, 0),
|
||||
('cat-iot', 'Smart Home & IoT', 'smart-home-iot', 'Home automation, smart devices, Philips Hue, Sonos, sensors, and IoT integrations', 'Home', '#06B6D4', 17, 0),
|
||||
('cat-multimedia', 'Multimedia & Audio/Video', 'multimedia-audio-video', 'Music, video, audio processing, Spotify, FFmpeg, text-to-speech, and media tools', 'Music', '#F43F5E', 18, 0),
|
||||
('cat-social', 'Social & Communications', 'social-communications', 'Twitter, messaging, email clients, social media automation, and chat integrations', 'MessageCircle', '#0EA5E9', 19, 0),
|
||||
('cat-business', 'Business & Finance', 'business-finance', 'Payments, invoicing, financial modeling, market analysis, and business automation', 'Briefcase', '#22C55E', 20, 0),
|
||||
('cat-science', 'Science & Mathematics', 'science-mathematics', 'Math problem solving, scientific computing, chemistry, physics, and research tools', 'Calculator', '#8B5CF6', 21, 0),
|
||||
('cat-blockchain', 'Blockchain & Web3', 'blockchain-web3', 'DeFi, smart contracts, Ethereum, Solana, NFTs, and cryptocurrency tools', 'Coins', '#F59E0B', 22, 0)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
slug = EXCLUDED.slug,
|
||||
description = EXCLUDED.description,
|
||||
icon = EXCLUDED.icon,
|
||||
color = EXCLUDED.color,
|
||||
sort_order = EXCLUDED.sort_order;
|
||||
|
||||
-- Update cat-other to have the highest sort_order
|
||||
UPDATE categories SET sort_order = 23 WHERE id = 'cat-other';
|
||||
|
||||
-- Show result
|
||||
SELECT id, name, skill_count, sort_order FROM categories ORDER BY sort_order;
|
||||
96
scripts/add-parent-categories.sql
Normal file
96
scripts/add-parent-categories.sql
Normal file
@@ -0,0 +1,96 @@
|
||||
-- Add Parent Categories for Hierarchical Structure (Phase 2)
|
||||
-- Version: 1.0
|
||||
-- Date: February 2026
|
||||
--
|
||||
-- This script adds 7 parent "chapter" categories and links existing 23 categories to them.
|
||||
-- Parent categories have skill_count = 0 (skills are only assigned to leaf categories).
|
||||
|
||||
-- Insert 7 parent categories
|
||||
INSERT INTO categories (id, name, slug, description, icon, color, sort_order, skill_count, created_at)
|
||||
VALUES
|
||||
('parent-dev', 'Development', 'development', 'Software development and programming tools', 'Code2', '#3B82F6', 1, 0, NOW()),
|
||||
('parent-ai', 'AI & Automation', 'ai-automation', 'Artificial intelligence and automation tools', 'Brain', '#8B5CF6', 2, 0, NOW()),
|
||||
('parent-data', 'Data & Documents', 'data-documents', 'Data management and document processing', 'Database', '#10B981', 3, 0, NOW()),
|
||||
('parent-devops', 'DevOps & Security', 'devops-security', 'Infrastructure, deployment, and security tools', 'Cloud', '#F97316', 4, 0, NOW()),
|
||||
('parent-business', 'Business & Productivity', 'business-productivity', 'Business tools and productivity applications', 'Briefcase', '#22C55E', 5, 0, NOW()),
|
||||
('parent-media', 'Media & IoT', 'media-iot', 'Multimedia processing and smart device integrations', 'Music', '#F43F5E', 6, 0, NOW()),
|
||||
('parent-specialized', 'Specialized', 'specialized', 'Specialized tools for niche domains', 'Sparkles', '#6B7280', 7, 0, NOW())
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
slug = EXCLUDED.slug,
|
||||
description = EXCLUDED.description,
|
||||
icon = EXCLUDED.icon,
|
||||
color = EXCLUDED.color,
|
||||
sort_order = EXCLUDED.sort_order;
|
||||
|
||||
-- Link child categories to parents
|
||||
-- Development group (4 categories)
|
||||
UPDATE categories SET parent_id = 'parent-dev' WHERE id IN ('cat-backend', 'cat-frontend', 'cat-mobile', 'cat-languages');
|
||||
|
||||
-- AI & Automation group (3 categories)
|
||||
UPDATE categories SET parent_id = 'parent-ai' WHERE id IN ('cat-ai-llm', 'cat-agents', 'cat-prompts');
|
||||
|
||||
-- Data & Documents group (2 categories)
|
||||
UPDATE categories SET parent_id = 'parent-data' WHERE id IN ('cat-data', 'cat-documents');
|
||||
|
||||
-- DevOps & Security group (4 categories)
|
||||
UPDATE categories SET parent_id = 'parent-devops' WHERE id IN ('cat-devops', 'cat-git', 'cat-testing', 'cat-security');
|
||||
|
||||
-- Business & Productivity group (4 categories)
|
||||
UPDATE categories SET parent_id = 'parent-business' WHERE id IN ('cat-productivity', 'cat-business', 'cat-social', 'cat-content');
|
||||
|
||||
-- Media & IoT group (2 categories)
|
||||
UPDATE categories SET parent_id = 'parent-media' WHERE id IN ('cat-multimedia', 'cat-iot');
|
||||
|
||||
-- Specialized group (4 categories)
|
||||
UPDATE categories SET parent_id = 'parent-specialized' WHERE id IN ('cat-science', 'cat-blockchain', 'cat-mcp', 'cat-other');
|
||||
|
||||
-- Update sort_order for child categories within their groups
|
||||
-- Development children (sort: 10-19)
|
||||
UPDATE categories SET sort_order = 10 WHERE id = 'cat-backend';
|
||||
UPDATE categories SET sort_order = 11 WHERE id = 'cat-frontend';
|
||||
UPDATE categories SET sort_order = 12 WHERE id = 'cat-mobile';
|
||||
UPDATE categories SET sort_order = 13 WHERE id = 'cat-languages';
|
||||
|
||||
-- AI children (sort: 20-29)
|
||||
UPDATE categories SET sort_order = 20 WHERE id = 'cat-ai-llm';
|
||||
UPDATE categories SET sort_order = 21 WHERE id = 'cat-agents';
|
||||
UPDATE categories SET sort_order = 22 WHERE id = 'cat-prompts';
|
||||
|
||||
-- Data children (sort: 30-39)
|
||||
UPDATE categories SET sort_order = 30 WHERE id = 'cat-data';
|
||||
UPDATE categories SET sort_order = 31 WHERE id = 'cat-documents';
|
||||
|
||||
-- DevOps children (sort: 40-49)
|
||||
UPDATE categories SET sort_order = 40 WHERE id = 'cat-devops';
|
||||
UPDATE categories SET sort_order = 41 WHERE id = 'cat-git';
|
||||
UPDATE categories SET sort_order = 42 WHERE id = 'cat-testing';
|
||||
UPDATE categories SET sort_order = 43 WHERE id = 'cat-security';
|
||||
|
||||
-- Business children (sort: 50-59)
|
||||
UPDATE categories SET sort_order = 50 WHERE id = 'cat-productivity';
|
||||
UPDATE categories SET sort_order = 51 WHERE id = 'cat-business';
|
||||
UPDATE categories SET sort_order = 52 WHERE id = 'cat-social';
|
||||
UPDATE categories SET sort_order = 53 WHERE id = 'cat-content';
|
||||
|
||||
-- Media children (sort: 60-69)
|
||||
UPDATE categories SET sort_order = 60 WHERE id = 'cat-multimedia';
|
||||
UPDATE categories SET sort_order = 61 WHERE id = 'cat-iot';
|
||||
|
||||
-- Specialized children (sort: 70-79)
|
||||
UPDATE categories SET sort_order = 70 WHERE id = 'cat-science';
|
||||
UPDATE categories SET sort_order = 71 WHERE id = 'cat-blockchain';
|
||||
UPDATE categories SET sort_order = 72 WHERE id = 'cat-mcp';
|
||||
UPDATE categories SET sort_order = 73 WHERE id = 'cat-other';
|
||||
|
||||
-- Verify the hierarchy
|
||||
SELECT
|
||||
COALESCE(p.name, '(No Parent)') as parent,
|
||||
c.id,
|
||||
c.name,
|
||||
c.skill_count,
|
||||
c.sort_order
|
||||
FROM categories c
|
||||
LEFT JOIN categories p ON c.parent_id = p.id
|
||||
WHERE c.id NOT LIKE 'parent-%'
|
||||
ORDER BY COALESCE(p.sort_order, 999), c.sort_order;
|
||||
53
scripts/categories.sql
Normal file
53
scripts/categories.sql
Normal file
@@ -0,0 +1,53 @@
|
||||
-- SkillHub Categories v3.0
|
||||
-- Run this for production deployment to initialize 23 standard categories
|
||||
-- Updated: February 2026 - Based on analysis of 171,509 skills and council review
|
||||
-- Goal: Reduce "Other" category from 19.9% to <10%
|
||||
|
||||
-- Clear existing category links (keep skills intact)
|
||||
DELETE FROM skill_categories;
|
||||
DELETE FROM categories;
|
||||
|
||||
-- Insert 16 categories ordered by expected volume (largest first for better UX)
|
||||
INSERT INTO categories (id, name, slug, description, icon, color, sort_order, skill_count) VALUES
|
||||
-- Tier 1: High volume categories (15-20% each)
|
||||
('cat-ai-llm', 'AI & LLM', 'ai-llm', 'Large language models, Claude, OpenAI, LangChain, RAG, embeddings, and machine learning', 'Brain', '#8B5CF6', 1, 0),
|
||||
('cat-git', 'Git & Version Control', 'git-version-control', 'GitHub, GitLab, branching, merging, pull requests, and repository management', 'GitBranch', '#6366F1', 2, 0),
|
||||
('cat-data', 'Data & Database', 'data-database', 'SQL, NoSQL, PostgreSQL, MongoDB, Redis, ORM, ETL, and data pipelines', 'Database', '#10B981', 3, 0),
|
||||
('cat-backend', 'Backend & APIs', 'backend-apis', 'REST, GraphQL, Express, FastAPI, Django, microservices, and server-side development', 'Server', '#3B82F6', 4, 0),
|
||||
|
||||
-- Tier 2: Medium volume (6-12% each)
|
||||
('cat-frontend', 'Frontend & UI', 'frontend-ui', 'React, Vue, Svelte, CSS, Tailwind, design systems, and component libraries', 'Monitor', '#EC4899', 5, 0),
|
||||
('cat-agents', 'Agents & Orchestration', 'agents-orchestration', 'Multi-agent systems, agentic workflows, autonomous agents, and AI orchestration', 'Bot', '#7C3AED', 6, 0),
|
||||
('cat-testing', 'Testing & QA', 'testing-qa', 'Jest, Cypress, Playwright, TDD, unit tests, debugging, and quality assurance', 'CheckCircle', '#10B981', 7, 0),
|
||||
('cat-devops', 'DevOps & Cloud', 'devops-cloud', 'Docker, Kubernetes, AWS, Azure, GCP, CI/CD, Terraform, and infrastructure', 'Cloud', '#F97316', 8, 0),
|
||||
('cat-languages', 'Programming Languages', 'programming-languages', 'Python, JavaScript, TypeScript, Rust, Go, Java, and language-specific patterns', 'Code', '#14B8A6', 9, 0),
|
||||
|
||||
-- Tier 3: Specialized (2-5% each)
|
||||
('cat-documents', 'Documents & Files', 'documents-files', 'PDF, Word, Excel, PowerPoint, file conversion, OCR, and document processing', 'FileText', '#3B82F6', 10, 0),
|
||||
('cat-security', 'Security & Auth', 'security-auth', 'OAuth, JWT, encryption, authentication, authorization, and vulnerability scanning', 'Shield', '#EF4444', 11, 0),
|
||||
('cat-mcp', 'MCP & Skills', 'mcp-skills', 'Model Context Protocol, skill creation, superpowers, and agent capabilities', 'Layers', '#A855F7', 12, 0),
|
||||
('cat-prompts', 'Prompts & Instructions', 'prompts-instructions', 'Prompt engineering, chain-of-thought, few-shot learning, and instruction design', 'Sparkles', '#FBBF24', 13, 0),
|
||||
('cat-content', 'Content & Writing', 'content-writing', 'Documentation, technical writing, i18n, localization, and content creation', 'PenTool', '#F59E0B', 14, 0),
|
||||
('cat-mobile', 'Mobile Development', 'mobile-development', 'iOS, Android, React Native, Flutter, Expo, and cross-platform mobile apps', 'Smartphone', '#A855F7', 15, 0),
|
||||
|
||||
-- Tier 3.5: New specialized categories (to reduce "Other" bloat)
|
||||
('cat-productivity', 'Productivity & Notes', 'productivity-notes', 'Note-taking, reminders, task management, calendar, and personal productivity tools', 'StickyNote', '#84CC16', 16, 0),
|
||||
('cat-iot', 'Smart Home & IoT', 'smart-home-iot', 'Home automation, smart devices, Philips Hue, Sonos, sensors, and IoT integrations', 'Home', '#06B6D4', 17, 0),
|
||||
('cat-multimedia', 'Multimedia & Audio/Video', 'multimedia-audio-video', 'Music, video, audio processing, Spotify, FFmpeg, text-to-speech, and media tools', 'Music', '#F43F5E', 18, 0),
|
||||
('cat-social', 'Social & Communications', 'social-communications', 'Twitter, messaging, email clients, social media automation, and chat integrations', 'MessageCircle', '#0EA5E9', 19, 0),
|
||||
('cat-business', 'Business & Finance', 'business-finance', 'Payments, invoicing, financial modeling, market analysis, and business automation', 'Briefcase', '#22C55E', 20, 0),
|
||||
('cat-science', 'Science & Mathematics', 'science-mathematics', 'Math problem solving, scientific computing, chemistry, physics, and research tools', 'Calculator', '#8B5CF6', 21, 0),
|
||||
('cat-blockchain', 'Blockchain & Web3', 'blockchain-web3', 'DeFi, smart contracts, Ethereum, Solana, NFTs, and cryptocurrency tools', 'Coins', '#F59E0B', 22, 0),
|
||||
|
||||
-- Tier 4: Fallback
|
||||
('cat-other', 'Other & Utilities', 'other-utilities', 'Miscellaneous tools and utilities not fitting other categories', 'Package', '#6B7280', 23, 0)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
slug = EXCLUDED.slug,
|
||||
description = EXCLUDED.description,
|
||||
icon = EXCLUDED.icon,
|
||||
color = EXCLUDED.color,
|
||||
sort_order = EXCLUDED.sort_order;
|
||||
|
||||
-- Show result
|
||||
SELECT 'Categories initialized: ' || count(*) as result FROM categories;
|
||||
228
scripts/generate-cli-demo.mjs
Normal file
228
scripts/generate-cli-demo.mjs
Normal file
@@ -0,0 +1,228 @@
|
||||
import { chromium } from 'playwright';
|
||||
import { join } from 'path';
|
||||
import { mkdirSync, existsSync, unlinkSync, readdirSync } from 'fs';
|
||||
|
||||
const SCREENSHOTS_DIR = './screenshots/cli-demo';
|
||||
|
||||
// Ensure directory exists
|
||||
if (!existsSync(SCREENSHOTS_DIR)) {
|
||||
mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function generateTerminalHTML(prompt, output, status, showCursor) {
|
||||
if (showCursor === undefined) showCursor = true;
|
||||
const cursorHtml = showCursor ? '<span class="cursor"></span>' : '';
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
width: 800px;
|
||||
height: 500px;
|
||||
background: #0d1117;
|
||||
font-family: 'JetBrains Mono', 'Consolas', monospace;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.window {
|
||||
background: #161b22;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #30363d;
|
||||
margin: 20px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
background: #21262d;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid #30363d;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.red { background: #ff5f56; }
|
||||
.dot.yellow { background: #ffbd2e; }
|
||||
.dot.green { background: #27c93f; }
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #8b949e;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #c9d1d9;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prompt { color: #7ee787; }
|
||||
.command { color: #79c0ff; }
|
||||
.success { color: #7ee787; }
|
||||
.muted { color: #8b949e; }
|
||||
.skill-id { color: #79c0ff; font-weight: 500; }
|
||||
.stars { color: #f7c150; }
|
||||
.downloads { color: #a5d6ff; }
|
||||
.desc { color: #8b949e; }
|
||||
.divider { color: #30363d; }
|
||||
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 18px;
|
||||
background: #c9d1d9;
|
||||
animation: blink 1s step-end infinite;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 8px 16px;
|
||||
background: #21262d;
|
||||
color: #8b949e;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #30363d;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #66b3e6 0%, #f7c150 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="window">
|
||||
<div class="titlebar">
|
||||
<div class="dot red"></div>
|
||||
<div class="dot yellow"></div>
|
||||
<div class="dot green"></div>
|
||||
<div class="title">Terminal — skillhub</div>
|
||||
</div>
|
||||
<div class="terminal">
|
||||
<span class="prompt">${prompt}</span>${cursorHtml}
|
||||
${output}
|
||||
</div>
|
||||
<div class="status">${status}</div>
|
||||
</div>
|
||||
<div class="logo">SkillHub</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
// CLI Demo frames - 4 distinct frames
|
||||
const FRAMES = [
|
||||
// Frame 1: Empty prompt, ready to type
|
||||
{
|
||||
content: generateTerminalHTML('$ ', '', 'Ready'),
|
||||
description: 'Empty prompt'
|
||||
},
|
||||
|
||||
// Frame 2: Command typed
|
||||
{
|
||||
content: generateTerminalHTML('$ npx skillhub search pdf', '', 'Press Enter to search...'),
|
||||
description: 'Command typed'
|
||||
},
|
||||
|
||||
// Frame 3: Searching animation
|
||||
{
|
||||
content: generateTerminalHTML('$ npx skillhub search pdf', `
|
||||
|
||||
<span class="muted">⠋ Searching skills...</span>
|
||||
`, 'Searching...', false),
|
||||
description: 'Searching'
|
||||
},
|
||||
|
||||
// Frame 4: Results displayed (final state)
|
||||
{
|
||||
content: generateTerminalHTML('$ npx skillhub search pdf', `
|
||||
|
||||
<span class="success">✔ Found 156 skills:</span>
|
||||
|
||||
<span class="divider">─────────────────────────────────────────────────────────────────</span>
|
||||
<span class="skill-id">anthropics/skills/pdf</span> <span class="stars">⭐ 30.2k</span> <span class="downloads">⬇ 1.2k</span>
|
||||
<span class="desc">Read, extract and manipulate PDF files. Convert PDFs...</span>
|
||||
<span class="divider">─────────────────────────────────────────────────────────────────</span>
|
||||
<span class="skill-id">obra/superpowers/pdf-extraction</span> <span class="stars">⭐ 8.1k</span> <span class="downloads">⬇ 421</span>
|
||||
<span class="desc">Advanced PDF extraction with OCR support and table...</span>
|
||||
<span class="divider">─────────────────────────────────────────────────────────────────</span>
|
||||
<span class="skill-id">openai/skills/pdf-reader</span> <span class="stars">⭐ 2.2k</span> <span class="downloads">⬇ 189</span>
|
||||
<span class="desc">Parse PDF documents and extract structured text...</span>
|
||||
<span class="divider">─────────────────────────────────────────────────────────────────</span>
|
||||
|
||||
<span class="muted">Install with:</span> <span class="command">npx skillhub install <skill-id></span>
|
||||
<span class="muted">Showing 3 of 156. Use --limit to see more.</span>
|
||||
`, 'Done - 156 skills found', false),
|
||||
description: 'Results'
|
||||
},
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log('=== CLI Demo Animation Generator ===\n');
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 800, height: 500 },
|
||||
deviceScaleFactor: 2,
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
// Clean up old frames
|
||||
const existingFiles = readdirSync(SCREENSHOTS_DIR);
|
||||
for (const file of existingFiles) {
|
||||
if (file.endsWith('.png')) {
|
||||
unlinkSync(join(SCREENSHOTS_DIR, file));
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Generating 4 distinct frames...');
|
||||
|
||||
for (let i = 0; i < FRAMES.length; i++) {
|
||||
const frame = FRAMES[i];
|
||||
await page.setContent(frame.content);
|
||||
await page.screenshot({
|
||||
path: join(SCREENSHOTS_DIR, 'frame-' + String(i).padStart(3, '0') + '.png'),
|
||||
});
|
||||
console.log(' Frame ' + (i + 1) + '/' + FRAMES.length + ': ' + frame.description);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
|
||||
console.log('\n=== Frames Generated ===');
|
||||
console.log('\nFrames saved to: ' + SCREENSHOTS_DIR + '/');
|
||||
console.log('\nTo create GIF:');
|
||||
console.log(' ffmpeg -framerate 0.5 -i frame-%03d.png -vf "fps=10,scale=800:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ../cli-demo.gif');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
280
scripts/generate-marketing-assets.mjs
Normal file
280
scripts/generate-marketing-assets.mjs
Normal file
@@ -0,0 +1,280 @@
|
||||
import { chromium } from 'playwright';
|
||||
import { writeFileSync, mkdirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://skills.palebluedot.live';
|
||||
const SCREENSHOTS_DIR = './screenshots';
|
||||
const OG_DIR = './apps/web/public/og';
|
||||
|
||||
// Read logo SVG and encode for inline use
|
||||
const logoSvg = readFileSync('./apps/web/public/logo.svg', 'utf-8');
|
||||
const logoBase64 = Buffer.from(logoSvg).toString('base64');
|
||||
|
||||
// Screenshots to capture (English locale, scroll past header)
|
||||
const SCREENSHOTS = [
|
||||
{ name: '01-homepage', url: '/en', scrollY: 0, description: 'Homepage with hero and featured skills' },
|
||||
{ name: '02-browse', url: '/en/browse', scrollY: 80, description: 'Browse page with filters and skill cards' },
|
||||
{ name: '03-skill-detail', url: '/en/skill/anthropics/skills/pdf', scrollY: 60, description: 'Skill detail page' },
|
||||
{ name: '04-categories', url: '/en/categories', scrollY: 80, description: 'Categories page' },
|
||||
{ name: '05-search-results', url: '/en/browse?q=python', scrollY: 80, description: 'Search results' },
|
||||
];
|
||||
|
||||
async function createOGImage(page) {
|
||||
console.log('Creating OG image (1200x630)...');
|
||||
|
||||
// Create a branded OG image with logo and evergreen stats
|
||||
const ogHTML = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 1200px;
|
||||
height: 630px;
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
|
||||
font-family: 'Inter', sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.glow {
|
||||
position: absolute;
|
||||
width: 700px;
|
||||
height: 700px;
|
||||
background: radial-gradient(circle, rgba(102, 179, 230, 0.12) 0%, transparent 70%);
|
||||
top: -150px;
|
||||
right: -150px;
|
||||
}
|
||||
|
||||
.glow-2 {
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: radial-gradient(circle, rgba(247, 193, 80, 0.08) 0%, transparent 70%);
|
||||
bottom: -100px;
|
||||
left: -100px;
|
||||
}
|
||||
|
||||
.content {
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
padding: 40px 60px;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 64px;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, #66b3e6 0%, #38bdf8 50%, #f7c150 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: 28px;
|
||||
color: #e2e8f0;
|
||||
font-weight: 500;
|
||||
margin-bottom: 50px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tagline span {
|
||||
color: #66b3e6;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 80px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 44px;
|
||||
font-weight: 700;
|
||||
color: #f7c150;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 16px;
|
||||
color: #94a3b8;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.platforms {
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.platform {
|
||||
background: rgba(102, 179, 230, 0.15);
|
||||
border: 1px solid rgba(102, 179, 230, 0.3);
|
||||
border-radius: 20px;
|
||||
padding: 8px 20px;
|
||||
color: #7dd3fc;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
font-size: 18px;
|
||||
color: #64748b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer .badge {
|
||||
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="glow"></div>
|
||||
<div class="glow-2"></div>
|
||||
<div class="content">
|
||||
<div class="logo-container">
|
||||
<img class="logo-icon" src="data:image/svg+xml;base64,${logoBase64}" alt="SkillHub Logo" />
|
||||
<div class="logo-text">SkillHub</div>
|
||||
</div>
|
||||
<div class="tagline">Open-Source Marketplace for <span>AI Agent Skills</span></div>
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-value">170K+</div>
|
||||
<div class="stat-label">Skills</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">4K+</div>
|
||||
<div class="stat-label">Contributors</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">30</div>
|
||||
<div class="stat-label">Categories</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="platforms">
|
||||
<span class="platform">Claude Code</span>
|
||||
<span class="platform">Codex CLI</span>
|
||||
<span class="platform">GitHub Copilot</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
${new URL(BASE_URL).hostname}
|
||||
<span class="badge">MIT License</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
await page.setContent(ogHTML);
|
||||
await page.setViewportSize({ width: 1200, height: 630 });
|
||||
await page.screenshot({
|
||||
path: join(OG_DIR, 'og-default.png'),
|
||||
type: 'png'
|
||||
});
|
||||
console.log(' Saved: og-default.png (1200x630)');
|
||||
}
|
||||
|
||||
async function captureScreenshots(page) {
|
||||
console.log('\nCapturing marketing screenshots (English)...');
|
||||
|
||||
for (const shot of SCREENSHOTS) {
|
||||
console.log(' Capturing: ' + shot.name + ' - ' + shot.description);
|
||||
|
||||
try {
|
||||
await page.goto(BASE_URL + shot.url, {
|
||||
waitUntil: 'networkidle',
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
// Wait for content to load
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Scroll past header if specified
|
||||
if (shot.scrollY > 0) {
|
||||
await page.evaluate((scrollY) => {
|
||||
window.scrollTo(0, scrollY);
|
||||
}, shot.scrollY);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
await page.screenshot({
|
||||
path: join(SCREENSHOTS_DIR, shot.name + '.png'),
|
||||
fullPage: false,
|
||||
});
|
||||
|
||||
console.log(' Saved: ' + shot.name + '.png');
|
||||
} catch (error) {
|
||||
console.error(' Error capturing ' + shot.name + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('=== SkillHub Marketing Assets Generator ===\n');
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
|
||||
// Create context with HiDPI settings for screenshots
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1440, height: 900 },
|
||||
deviceScaleFactor: 2, // Retina quality
|
||||
locale: 'en-US', // Force English
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
// Create OG Image first
|
||||
await createOGImage(page);
|
||||
|
||||
// Capture screenshots from live site (English)
|
||||
await captureScreenshots(page);
|
||||
|
||||
await browser.close();
|
||||
|
||||
console.log('\n=== Done! ===');
|
||||
console.log('\nOG Image: ' + OG_DIR + '/og-default.png');
|
||||
console.log('Screenshots: ' + SCREENSHOTS_DIR + '/');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
466
scripts/init-db.sql
Normal file
466
scripts/init-db.sql
Normal file
@@ -0,0 +1,466 @@
|
||||
-- SkillHub Database Initialization Script
|
||||
-- This script runs automatically when PostgreSQL container starts
|
||||
-- Matches the Drizzle ORM schema in packages/db/src/schema.ts
|
||||
|
||||
-- Enable UUID extension
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Enable full-text search
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
-- Create skills table (main entity)
|
||||
CREATE TABLE IF NOT EXISTS skills (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
|
||||
-- Source info
|
||||
github_owner TEXT NOT NULL,
|
||||
github_repo TEXT NOT NULL,
|
||||
skill_path TEXT NOT NULL,
|
||||
branch TEXT DEFAULT 'main',
|
||||
commit_sha TEXT,
|
||||
|
||||
-- Source format (which platform's instruction file format)
|
||||
source_format TEXT DEFAULT 'skill.md',
|
||||
|
||||
-- Metadata
|
||||
version TEXT,
|
||||
license TEXT,
|
||||
author TEXT,
|
||||
homepage TEXT,
|
||||
compatibility JSONB,
|
||||
triggers JSONB,
|
||||
|
||||
-- Quality signals
|
||||
github_stars INTEGER DEFAULT 0,
|
||||
github_forks INTEGER DEFAULT 0,
|
||||
download_count INTEGER DEFAULT 0,
|
||||
view_count INTEGER DEFAULT 0,
|
||||
|
||||
-- Ratings
|
||||
rating INTEGER,
|
||||
rating_count INTEGER DEFAULT 0,
|
||||
rating_sum INTEGER DEFAULT 0,
|
||||
|
||||
-- Security
|
||||
security_score INTEGER, -- 0-100 (deprecated, use security_status)
|
||||
security_status TEXT, -- 'pass', 'warning', 'fail'
|
||||
is_verified BOOLEAN DEFAULT FALSE,
|
||||
is_featured BOOLEAN DEFAULT FALSE,
|
||||
is_blocked BOOLEAN DEFAULT FALSE, -- Blocked from re-indexing (owner requested removal)
|
||||
last_scanned TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Content
|
||||
content_hash TEXT,
|
||||
raw_content TEXT,
|
||||
|
||||
-- Cached skill files (populated on first download)
|
||||
-- Structure: { fetchedAt, commitSha, totalSize, items: [{name, path, content, size, isBinary}] }
|
||||
cached_files JSONB,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
indexed_at TIMESTAMP WITH TIME ZONE,
|
||||
last_downloaded_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Create categories table
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
icon TEXT,
|
||||
color TEXT,
|
||||
parent_id TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
skill_count INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Create skill_categories junction table
|
||||
CREATE TABLE IF NOT EXISTS skill_categories (
|
||||
skill_id TEXT REFERENCES skills(id) ON DELETE CASCADE NOT NULL,
|
||||
category_id TEXT REFERENCES categories(id) ON DELETE CASCADE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
PRIMARY KEY (skill_id, category_id)
|
||||
);
|
||||
|
||||
-- Create users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::TEXT,
|
||||
github_id TEXT UNIQUE NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
email TEXT,
|
||||
avatar_url TEXT,
|
||||
bio TEXT,
|
||||
preferred_locale TEXT,
|
||||
is_admin BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
last_login_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Create ratings table
|
||||
CREATE TABLE IF NOT EXISTS ratings (
|
||||
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::TEXT,
|
||||
skill_id TEXT REFERENCES skills(id) ON DELETE CASCADE NOT NULL,
|
||||
user_id TEXT REFERENCES users(id) ON DELETE CASCADE NOT NULL,
|
||||
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||
review TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
UNIQUE (skill_id, user_id)
|
||||
);
|
||||
|
||||
-- Create installations table (anonymous tracking)
|
||||
CREATE TABLE IF NOT EXISTS installations (
|
||||
id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::TEXT,
|
||||
skill_id TEXT REFERENCES skills(id) ON DELETE CASCADE NOT NULL,
|
||||
platform TEXT NOT NULL,
|
||||
method TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Create favorites table
|
||||
CREATE TABLE IF NOT EXISTS favorites (
|
||||
user_id TEXT REFERENCES users(id) ON DELETE CASCADE NOT NULL,
|
||||
skill_id TEXT REFERENCES skills(id) ON DELETE CASCADE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
PRIMARY KEY (user_id, skill_id)
|
||||
);
|
||||
|
||||
-- Create indexing_jobs table
|
||||
CREATE TABLE IF NOT EXISTS indexing_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
skill_id TEXT,
|
||||
started_at TIMESTAMP WITH TIME ZONE,
|
||||
completed_at TIMESTAMP WITH TIME ZONE,
|
||||
error TEXT,
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Create discovered_repos table (for multi-strategy discovery)
|
||||
CREATE TABLE IF NOT EXISTS discovered_repos (
|
||||
id TEXT PRIMARY KEY, -- owner/repo
|
||||
owner TEXT NOT NULL,
|
||||
repo TEXT NOT NULL,
|
||||
discovered_via TEXT NOT NULL, -- 'awesome-list', 'topic-search', 'fork', 'org-scan', 'code-search'
|
||||
source_url TEXT, -- URL or reference to what discovered this repo
|
||||
last_scanned TIMESTAMP WITH TIME ZONE,
|
||||
skill_count INTEGER DEFAULT 0,
|
||||
has_skill_md BOOLEAN DEFAULT FALSE,
|
||||
github_stars INTEGER DEFAULT 0,
|
||||
github_forks INTEGER DEFAULT 0,
|
||||
default_branch TEXT DEFAULT 'main',
|
||||
is_archived BOOLEAN DEFAULT FALSE,
|
||||
scan_error TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Create awesome_lists table (for tracking curated lists)
|
||||
CREATE TABLE IF NOT EXISTS awesome_lists (
|
||||
id TEXT PRIMARY KEY, -- owner/repo
|
||||
owner TEXT NOT NULL,
|
||||
repo TEXT NOT NULL,
|
||||
name TEXT,
|
||||
last_parsed TIMESTAMP WITH TIME ZONE,
|
||||
repo_count INTEGER DEFAULT 0,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Create removal_requests table (for skill removal requests)
|
||||
CREATE TABLE IF NOT EXISTS removal_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
skill_id TEXT NOT NULL, -- Can reference non-existent skill if already removed
|
||||
reason TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending', -- pending, approved, rejected
|
||||
verified_owner BOOLEAN DEFAULT FALSE, -- GitHub API verification result
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
resolved_at TIMESTAMP WITH TIME ZONE,
|
||||
resolved_by TEXT REFERENCES users(id),
|
||||
resolution_note TEXT
|
||||
);
|
||||
|
||||
-- Create add_requests table (for skill addition requests)
|
||||
CREATE TABLE IF NOT EXISTS add_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
repository_url TEXT NOT NULL, -- Full GitHub URL
|
||||
skill_path TEXT, -- Optional path within repo (for subfolder skills)
|
||||
reason TEXT NOT NULL, -- Why should this skill be added
|
||||
status TEXT NOT NULL DEFAULT 'pending', -- pending, approved, rejected, indexed
|
||||
valid_repo BOOLEAN DEFAULT FALSE, -- GitHub API validation result
|
||||
has_skill_md BOOLEAN DEFAULT FALSE, -- Whether SKILL.md was found
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
processed_at TIMESTAMP WITH TIME ZONE,
|
||||
indexed_skill_id TEXT, -- Reference to skill if successfully indexed
|
||||
error_message TEXT -- Error if indexing failed
|
||||
);
|
||||
|
||||
-- Create indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_owner ON skills(github_owner);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_stars ON skills(github_stars DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_downloads ON skills(download_count DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_security ON skills(security_score DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_verified ON skills(is_verified);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_featured ON skills(is_featured);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_updated ON skills(updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_name_trgm ON skills USING gin (name gin_trgm_ops);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_desc_trgm ON skills USING gin (description gin_trgm_ops);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_categories_skill ON skill_categories(skill_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_categories_category ON skill_categories(category_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_github ON users(github_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ratings_skill ON ratings(skill_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ratings_user ON ratings(user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_installations_skill ON installations(skill_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_installations_platform ON installations(platform);
|
||||
CREATE INDEX IF NOT EXISTS idx_installations_created ON installations(created_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_favorites_user ON favorites(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_favorites_skill ON favorites(skill_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_indexing_jobs_status ON indexing_jobs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_indexing_jobs_type ON indexing_jobs(type);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_discovered_repos_owner ON discovered_repos(owner);
|
||||
CREATE INDEX IF NOT EXISTS idx_discovered_repos_discovered_via ON discovered_repos(discovered_via);
|
||||
CREATE INDEX IF NOT EXISTS idx_discovered_repos_last_scanned ON discovered_repos(last_scanned);
|
||||
CREATE INDEX IF NOT EXISTS idx_discovered_repos_skill_count ON discovered_repos(skill_count DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_discovered_repos_has_skill_md ON discovered_repos(has_skill_md);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_awesome_lists_last_parsed ON awesome_lists(last_parsed);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_removal_requests_user ON removal_requests(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_removal_requests_skill ON removal_requests(skill_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_removal_requests_status ON removal_requests(status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_add_requests_user ON add_requests(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_add_requests_status ON add_requests(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_add_requests_repo ON add_requests(repository_url);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_blocked ON skills(is_blocked);
|
||||
|
||||
-- Composite indexes for common query patterns (scalability optimization)
|
||||
-- These optimize filtering + sorting combinations for 5000+ skills
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_verified_stars ON skills(is_verified, github_stars DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_verified_downloads ON skills(is_verified, download_count DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_featured_stars ON skills(is_featured, github_stars DESC);
|
||||
|
||||
-- Category + sort indexes (via skill_categories join)
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_categories_cat_skill ON skill_categories(category_id, skill_id);
|
||||
|
||||
-- JSON field index for platform filtering
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_compatibility ON skills USING gin (compatibility);
|
||||
|
||||
-- Insert 16 standard categories (v2.0 - February 2026)
|
||||
-- Based on analysis of 171,509 skills - ordered by expected volume
|
||||
INSERT INTO categories (id, name, slug, description, icon, color, sort_order, skill_count) VALUES
|
||||
-- Tier 1: High volume categories (15-20% each)
|
||||
('cat-ai-llm', 'AI & LLM', 'ai-llm', 'Large language models, Claude, OpenAI, LangChain, RAG, embeddings, and machine learning', 'Brain', '#8B5CF6', 1, 0),
|
||||
('cat-git', 'Git & Version Control', 'git-version-control', 'GitHub, GitLab, branching, merging, pull requests, and repository management', 'GitBranch', '#6366F1', 2, 0),
|
||||
('cat-data', 'Data & Database', 'data-database', 'SQL, NoSQL, PostgreSQL, MongoDB, Redis, ORM, ETL, and data pipelines', 'Database', '#10B981', 3, 0),
|
||||
('cat-backend', 'Backend & APIs', 'backend-apis', 'REST, GraphQL, Express, FastAPI, Django, microservices, and server-side development', 'Server', '#3B82F6', 4, 0),
|
||||
-- Tier 2: Medium volume (6-12% each)
|
||||
('cat-frontend', 'Frontend & UI', 'frontend-ui', 'React, Vue, Svelte, CSS, Tailwind, design systems, and component libraries', 'Monitor', '#EC4899', 5, 0),
|
||||
('cat-agents', 'Agents & Orchestration', 'agents-orchestration', 'Multi-agent systems, agentic workflows, autonomous agents, and AI orchestration', 'Bot', '#7C3AED', 6, 0),
|
||||
('cat-testing', 'Testing & QA', 'testing-qa', 'Jest, Cypress, Playwright, TDD, unit tests, debugging, and quality assurance', 'CheckCircle', '#10B981', 7, 0),
|
||||
('cat-devops', 'DevOps & Cloud', 'devops-cloud', 'Docker, Kubernetes, AWS, Azure, GCP, CI/CD, Terraform, and infrastructure', 'Cloud', '#F97316', 8, 0),
|
||||
('cat-languages', 'Programming Languages', 'programming-languages', 'Python, JavaScript, TypeScript, Rust, Go, Java, and language-specific patterns', 'Code', '#14B8A6', 9, 0),
|
||||
-- Tier 3: Specialized (2-5% each)
|
||||
('cat-documents', 'Documents & Files', 'documents-files', 'PDF, Word, Excel, PowerPoint, file conversion, OCR, and document processing', 'FileText', '#3B82F6', 10, 0),
|
||||
('cat-security', 'Security & Auth', 'security-auth', 'OAuth, JWT, encryption, authentication, authorization, and vulnerability scanning', 'Shield', '#EF4444', 11, 0),
|
||||
('cat-mcp', 'MCP & Skills', 'mcp-skills', 'Model Context Protocol, skill creation, superpowers, and agent capabilities', 'Layers', '#A855F7', 12, 0),
|
||||
('cat-prompts', 'Prompts & Instructions', 'prompts-instructions', 'Prompt engineering, chain-of-thought, few-shot learning, and instruction design', 'Sparkles', '#FBBF24', 13, 0),
|
||||
('cat-content', 'Content & Writing', 'content-writing', 'Documentation, technical writing, i18n, localization, and content creation', 'PenTool', '#F59E0B', 14, 0),
|
||||
('cat-mobile', 'Mobile Development', 'mobile-development', 'iOS, Android, React Native, Flutter, Expo, and cross-platform mobile apps', 'Smartphone', '#A855F7', 15, 0),
|
||||
-- Tier 4: Fallback
|
||||
('cat-other', 'Other & Utilities', 'other-utilities', 'Miscellaneous tools and utilities not fitting other categories', 'Package', '#6B7280', 16, 0)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
slug = EXCLUDED.slug,
|
||||
description = EXCLUDED.description,
|
||||
icon = EXCLUDED.icon,
|
||||
color = EXCLUDED.color,
|
||||
sort_order = EXCLUDED.sort_order;
|
||||
|
||||
-- Full-text search vector column for relevance ranking
|
||||
-- This provides better search results than trigram matching alone
|
||||
ALTER TABLE skills ADD COLUMN IF NOT EXISTS search_vector tsvector;
|
||||
|
||||
-- Populate search vector for existing rows
|
||||
UPDATE skills SET search_vector =
|
||||
setweight(to_tsvector('english', coalesce(name, '')), 'A') ||
|
||||
setweight(to_tsvector('english', coalesce(description, '')), 'B') ||
|
||||
setweight(to_tsvector('english', coalesce(github_owner, '')), 'C')
|
||||
WHERE search_vector IS NULL;
|
||||
|
||||
-- Create GIN index for fast full-text search
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_search_vector ON skills USING GIN(search_vector);
|
||||
|
||||
-- Create function to update search vector on insert/update
|
||||
CREATE OR REPLACE FUNCTION skills_search_trigger() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.search_vector :=
|
||||
setweight(to_tsvector('english', coalesce(NEW.name, '')), 'A') ||
|
||||
setweight(to_tsvector('english', coalesce(NEW.description, '')), 'B') ||
|
||||
setweight(to_tsvector('english', coalesce(NEW.github_owner, '')), 'C');
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create trigger to keep search_vector updated
|
||||
DROP TRIGGER IF EXISTS skills_search_update ON skills;
|
||||
CREATE TRIGGER skills_search_update BEFORE INSERT OR UPDATE ON skills
|
||||
FOR EACH ROW EXECUTE FUNCTION skills_search_trigger();
|
||||
|
||||
-- Create function to update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Create skills-specific function that ignores counter-only and metadata-only updates
|
||||
-- Without this, every view/download/cache-fill would mark the skill as "updated"
|
||||
CREATE OR REPLACE FUNCTION update_skills_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Compare only content-related columns; ignore counters, cache, and indexer metadata
|
||||
-- Excluded: view_count, download_count, rating*, last_downloaded_at (counters)
|
||||
-- Excluded: cached_files (download cache, not content change)
|
||||
-- Excluded: indexed_at, last_scanned (indexer bookkeeping)
|
||||
-- Excluded: github_stars, github_forks (popularity metrics, not content)
|
||||
IF ROW(NEW.name, NEW.description, NEW.github_owner, NEW.github_repo, NEW.skill_path,
|
||||
NEW.branch, NEW.commit_sha, NEW.source_format, NEW.version, NEW.license, NEW.author, NEW.homepage,
|
||||
NEW.compatibility, NEW.triggers,
|
||||
NEW.security_score, NEW.security_status, NEW.is_verified, NEW.is_featured, NEW.is_blocked,
|
||||
NEW.content_hash, NEW.raw_content)
|
||||
IS NOT DISTINCT FROM
|
||||
ROW(OLD.name, OLD.description, OLD.github_owner, OLD.github_repo, OLD.skill_path,
|
||||
OLD.branch, OLD.commit_sha, OLD.source_format, OLD.version, OLD.license, OLD.author, OLD.homepage,
|
||||
OLD.compatibility, OLD.triggers,
|
||||
OLD.security_score, OLD.security_status, OLD.is_verified, OLD.is_featured, OLD.is_blocked,
|
||||
OLD.content_hash, OLD.raw_content)
|
||||
THEN
|
||||
-- No content change, preserve old updated_at
|
||||
NEW.updated_at = OLD.updated_at;
|
||||
ELSE
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Create trigger for skills table
|
||||
DROP TRIGGER IF EXISTS update_skills_updated_at ON skills;
|
||||
CREATE TRIGGER update_skills_updated_at
|
||||
BEFORE UPDATE ON skills
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_skills_updated_at_column();
|
||||
|
||||
-- Create trigger for users table
|
||||
DROP TRIGGER IF EXISTS update_users_updated_at ON users;
|
||||
CREATE TRIGGER update_users_updated_at
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Create trigger for ratings table
|
||||
DROP TRIGGER IF EXISTS update_ratings_updated_at ON ratings;
|
||||
CREATE TRIGGER update_ratings_updated_at
|
||||
BEFORE UPDATE ON ratings
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Create trigger for discovered_repos table
|
||||
DROP TRIGGER IF EXISTS update_discovered_repos_updated_at ON discovered_repos;
|
||||
CREATE TRIGGER update_discovered_repos_updated_at
|
||||
BEFORE UPDATE ON discovered_repos
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Create function to update category skill count
|
||||
CREATE OR REPLACE FUNCTION update_category_skill_count()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
UPDATE categories SET skill_count = skill_count + 1 WHERE id = NEW.category_id;
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
UPDATE categories SET skill_count = skill_count - 1 WHERE id = OLD.category_id;
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Create trigger for skill_categories
|
||||
DROP TRIGGER IF EXISTS update_category_count ON skill_categories;
|
||||
CREATE TRIGGER update_category_count
|
||||
AFTER INSERT OR DELETE ON skill_categories
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_category_skill_count();
|
||||
|
||||
-- Email subscriptions table (newsletter and marketing)
|
||||
CREATE TABLE IF NOT EXISTS email_subscriptions (
|
||||
id TEXT PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
source TEXT NOT NULL, -- 'oauth', 'newsletter', 'claim', 'early-access'
|
||||
marketing_consent BOOLEAN DEFAULT false,
|
||||
consent_date TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
unsubscribed_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Indexes for email_subscriptions
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_email_subscriptions_email ON email_subscriptions(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_email_subscriptions_source ON email_subscriptions(source);
|
||||
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- Schema Migrations
|
||||
-- These ALTER TABLE statements ensure columns added after
|
||||
-- initial table creation are present on existing databases.
|
||||
-- (CREATE TABLE IF NOT EXISTS does NOT add new columns)
|
||||
-- ============================================================
|
||||
|
||||
-- Users table migrations
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS preferred_locale TEXT;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
-- Skills table migrations
|
||||
ALTER TABLE skills ADD COLUMN IF NOT EXISTS security_status TEXT;
|
||||
ALTER TABLE skills ADD COLUMN IF NOT EXISTS is_blocked BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE skills ADD COLUMN IF NOT EXISTS cached_files JSONB;
|
||||
|
||||
-- Skills indexes for new columns
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_security_status ON skills(security_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_blocked ON skills(is_blocked);
|
||||
ALTER TABLE skills ADD COLUMN IF NOT EXISTS source_format TEXT DEFAULT 'skill.md';
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_source_format ON skills(source_format);
|
||||
|
||||
-- Add last_downloaded_at column for sorting by recent downloads
|
||||
ALTER TABLE skills ADD COLUMN IF NOT EXISTS last_downloaded_at TIMESTAMP WITH TIME ZONE;
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_last_downloaded ON skills(last_downloaded_at DESC NULLS LAST);
|
||||
|
||||
-- Grant permissions
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO postgres;
|
||||
5
scripts/migrate-add-source-format.sql
Normal file
5
scripts/migrate-add-source-format.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Migration: Add source_format column to skills table
|
||||
-- Run: docker exec -i skillhub-db psql -U postgres -d skillhub < scripts/migrate-add-source-format.sql
|
||||
|
||||
ALTER TABLE skills ADD COLUMN IF NOT EXISTS source_format TEXT DEFAULT 'skill.md';
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_source_format ON skills(source_format);
|
||||
44
scripts/migrations/add-skill-management.sql
Normal file
44
scripts/migrations/add-skill-management.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- Migration: Add skill management features (is_blocked, add_requests table)
|
||||
-- Run this on existing databases to add the new columns and tables
|
||||
|
||||
-- Add is_blocked column to skills table (if not exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'skills' AND column_name = 'is_blocked'
|
||||
) THEN
|
||||
ALTER TABLE skills ADD COLUMN is_blocked BOOLEAN DEFAULT FALSE;
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_blocked ON skills(is_blocked);
|
||||
RAISE NOTICE 'Added is_blocked column to skills table';
|
||||
ELSE
|
||||
RAISE NOTICE 'is_blocked column already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Create add_requests table (if not exists)
|
||||
CREATE TABLE IF NOT EXISTS add_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
repository_url TEXT NOT NULL,
|
||||
skill_path TEXT,
|
||||
reason TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
valid_repo BOOLEAN DEFAULT FALSE,
|
||||
has_skill_md BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
processed_at TIMESTAMP WITH TIME ZONE,
|
||||
indexed_skill_id TEXT,
|
||||
error_message TEXT
|
||||
);
|
||||
|
||||
-- Create indexes for add_requests (if not exists)
|
||||
CREATE INDEX IF NOT EXISTS idx_add_requests_user ON add_requests(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_add_requests_status ON add_requests(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_add_requests_repo ON add_requests(repository_url);
|
||||
|
||||
-- Verify the changes
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Migration completed successfully';
|
||||
END $$;
|
||||
16
scripts/package.json
Normal file
16
scripts/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "scripts",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"playwright": "^1.58.1"
|
||||
}
|
||||
}
|
||||
58
scripts/seed-data.sql
Normal file
58
scripts/seed-data.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- SkillHub Sample Data Seed Script
|
||||
-- Run this after init-db.sql to populate sample data for development/testing
|
||||
|
||||
-- Insert sample skills
|
||||
INSERT INTO skills (id, name, description, github_owner, github_repo, skill_path, branch, version, license, github_stars, download_count, security_score, is_verified, compatibility, indexed_at) VALUES
|
||||
-- Anthropic Skills
|
||||
('anthropic/skills/code-review', 'code-review', 'Comprehensive code review skill that analyzes code quality, security, and best practices. Supports multiple programming languages and provides actionable feedback.', 'anthropic', 'skills', 'code-review', 'main', '2.1.0', 'MIT', 2847, 15420, 95, true, '{"platforms": ["claude", "codex"]}', NOW()),
|
||||
('anthropic/skills/security-audit', 'security-audit', 'Perform comprehensive security audits on your codebase. Identifies vulnerabilities, suggests fixes, and follows OWASP guidelines.', 'anthropic', 'skills', 'security-audit', 'main', '1.8.0', 'MIT', 1876, 9500, 99, true, '{"platforms": ["claude"]}', NOW()),
|
||||
('anthropic/skills/commit-message', 'commit-message', 'Generate meaningful and conventional commit messages based on your staged changes. Follows conventional commits specification.', 'anthropic', 'skills', 'commit-message', 'main', '1.3.0', 'MIT', 1650, 8900, 94, true, '{"platforms": ["claude", "copilot"]}', NOW()),
|
||||
|
||||
-- OpenAI Skills
|
||||
('openai/skills/test-generator', 'test-generator', 'Automatically generate unit tests for your code with comprehensive coverage. Supports Jest, Pytest, Go testing, and more.', 'openai', 'skills', 'test-generator', 'main', '1.5.0', 'MIT', 1923, 8932, 92, true, '{"platforms": ["codex", "copilot"]}', NOW()),
|
||||
|
||||
-- Microsoft Skills
|
||||
('microsoft/copilot-skills/refactor', 'refactor', 'Intelligent code refactoring with support for multiple programming languages. Improves code quality while preserving functionality.', 'microsoft', 'copilot-skills', 'refactor', 'main', '3.0.0', 'MIT', 3421, 21000, 97, true, '{"platforms": ["copilot", "claude"]}', NOW()),
|
||||
|
||||
-- Community Skills
|
||||
('community/skills/documentation', 'documentation', 'Generate beautiful documentation from your codebase automatically. Supports JSDoc, docstrings, and markdown output.', 'community', 'skills', 'documentation', 'main', '1.2.0', 'Apache-2.0', 1456, 6721, 88, false, '{"platforms": ["claude", "codex", "copilot"]}', NOW()),
|
||||
('community/ai-tools/api-generator', 'api-generator', 'Generate REST APIs from specifications automatically. Supports OpenAPI, GraphQL, and gRPC.', 'community', 'ai-tools', 'api-generator', 'main', '1.0.0', 'Apache-2.0', 890, 4200, 85, false, '{"platforms": ["claude", "codex"]}', NOW()),
|
||||
|
||||
-- Devin Skills
|
||||
('devin/skills/debug-assistant', 'debug-assistant', 'AI-powered debugging assistant that helps identify and fix bugs. Analyzes stack traces, suggests solutions, and explains errors.', 'devin', 'skills', 'debug-assistant', 'main', '2.0.0', 'MIT', 2100, 12000, 91, true, '{"platforms": ["claude", "codex", "copilot"]}', NOW()),
|
||||
|
||||
-- Additional skills for variety
|
||||
('cursor/skills/autocomplete-pro', 'autocomplete-pro', 'Advanced code autocomplete with context-aware suggestions. Learns from your codebase patterns.', 'cursor', 'skills', 'autocomplete-pro', 'main', '2.5.0', 'MIT', 4500, 32000, 93, true, '{"platforms": ["claude", "codex", "copilot"]}', NOW()),
|
||||
('github/copilot-skills/pr-review', 'pr-review', 'Automated pull request review with detailed feedback on code changes, potential issues, and suggestions.', 'github', 'copilot-skills', 'pr-review', 'main', '1.4.0', 'MIT', 2300, 14500, 96, true, '{"platforms": ["copilot", "claude"]}', NOW())
|
||||
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
description = EXCLUDED.description,
|
||||
version = EXCLUDED.version,
|
||||
github_stars = EXCLUDED.github_stars,
|
||||
download_count = EXCLUDED.download_count,
|
||||
security_score = EXCLUDED.security_score,
|
||||
indexed_at = NOW();
|
||||
|
||||
-- Link skills to categories (using IDs from categories.sql)
|
||||
INSERT INTO skill_categories (skill_id, category_id) VALUES
|
||||
('anthropic/skills/code-review', 'cat-development'),
|
||||
('anthropic/skills/security-audit', 'cat-security'),
|
||||
('anthropic/skills/commit-message', 'cat-productivity'),
|
||||
('openai/skills/test-generator', 'cat-testing'),
|
||||
('microsoft/copilot-skills/refactor', 'cat-development'),
|
||||
('community/skills/documentation', 'cat-writing'),
|
||||
('community/ai-tools/api-generator', 'cat-api'),
|
||||
('devin/skills/debug-assistant', 'cat-development'),
|
||||
('cursor/skills/autocomplete-pro', 'cat-development'),
|
||||
('github/copilot-skills/pr-review', 'cat-development')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Update category skill counts
|
||||
UPDATE categories SET skill_count = (
|
||||
SELECT COUNT(*) FROM skill_categories WHERE category_id = categories.id
|
||||
);
|
||||
|
||||
-- Show results
|
||||
SELECT 'Skills inserted:' as info, COUNT(*) as count FROM skills;
|
||||
SELECT 'Categories with skills:' as info, COUNT(*) as count FROM categories WHERE skill_count > 0;
|
||||
Reference in New Issue
Block a user