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:
airano
2026-02-12 06:08:51 +03:30
commit 97b427831a
227 changed files with 48411 additions and 0 deletions

326
docs/API.md Normal file
View File

@@ -0,0 +1,326 @@
# API Reference
SkillHub provides a public REST API for searching, browsing, and installing AI agent skills.
**Base URL:** `https://skills.palebluedot.live/api` (or your self-hosted instance)
---
## Authentication
Most read endpoints are public. Write operations require GitHub OAuth authentication via NextAuth.js.
### Rate Limits
| Tier | Limit | Description |
|------|-------|-------------|
| Anonymous | 100/min | Unauthenticated requests |
| Authenticated | 200/min | Logged-in users |
| Search | 60/min | Search queries |
---
## Skills
### Search Skills
```
GET /api/skills?q={query}&limit={limit}&offset={offset}
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `q` | string | - | Search query |
| `limit` | number | 20 | Results per page |
| `offset` | number | 0 | Pagination offset |
**Example:**
```bash
curl "https://skills.palebluedot.live/api/skills?q=pdf&limit=10"
```
### Get Skill Details
```
GET /api/skills/{owner}/{repo}/{skill-name}
```
Skill IDs use the format `owner/repo/skill-name`.
**Example:**
```bash
curl https://skills.palebluedot.live/api/skills/anthropics/skills/pdf
```
### Featured Skills
```
GET /api/skills/featured
```
Returns a curated list of featured skills. Response is cached.
### Recent Skills
```
GET /api/skills/recent
```
Returns recently updated skills. Response is cached.
### Track Installation
```
POST /api/skills/install
```
| Field | Type | Description |
|-------|------|-------------|
| `skillId` | string | Skill ID (`owner/repo/skill-name`) |
| `platform` | string | Target platform |
| `method` | string | Installation method |
### Submit Skill Addition Request
```
POST /api/skills/add-request
```
Requires authentication.
| Field | Type | Description |
|-------|------|-------------|
| `gitHubUrl` | string | GitHub repository URL |
| `skillPath` | string | Path to SKILL.md in repo |
| `reason` | string | Reason for submission |
### Submit Skill Removal Request
```
POST /api/skills/removal-request
```
Requires authentication.
| Field | Type | Description |
|-------|------|-------------|
| `skillId` | string | Skill to remove |
| `reason` | string | Reason for removal |
### Verify Ownership
```
GET /api/skills/verify-ownership?owner={owner}&repo={repo}
```
Requires authentication. Checks if the authenticated user owns the specified GitHub repository.
---
## Skill Files
### Get Skill Files
```
GET /api/skill-files?skillId={skillId}
```
Fetches skill file contents from GitHub (with caching).
### Download as ZIP
```
GET /api/skill-files/zip?skillId={skillId}&platform={platform}
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `skillId` | string | Skill ID |
| `platform` | string | `claude`, `codex`, `copilot`, `cursor`, or `windsurf` |
---
## Categories
### List Categories
```
GET /api/categories
```
Returns all skill categories with skill counts.
**Example:**
```bash
curl https://skills.palebluedot.live/api/categories
```
---
## Ratings
### Get Reviews
```
GET /api/ratings?skillId={skillId}&limit={limit}&offset={offset}
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `skillId` | string | required | Skill ID |
| `limit` | number | 10 | Results per page |
| `offset` | number | 0 | Pagination offset |
### Submit Rating
```
POST /api/ratings
```
Requires authentication.
| Field | Type | Description |
|-------|------|-------------|
| `skillId` | string | Skill ID |
| `rating` | number | Rating (1-5) |
| `review` | string | Review text |
### Get My Rating
```
GET /api/ratings/me?skillId={skillId}
```
Requires authentication. Returns the current user's rating for a skill.
---
## Favorites
### Get My Favorites
```
GET /api/favorites
```
Requires authentication. Returns the user's favorited skills.
### Check Favorites
```
POST /api/favorites/check
```
Requires authentication.
| Field | Type | Description |
|-------|------|-------------|
| `skillIds` | string[] | Array of skill IDs to check |
---
## Newsletter
### Subscribe
```
GET /api/newsletter/subscribe?email={email}&locale={locale}
```
One-click subscription from email links.
### Unsubscribe
```
POST /api/newsletter/unsubscribe
```
| Field | Type | Description |
|-------|------|-------------|
| `email` | string | Email to unsubscribe |
---
## Platform
### Statistics
```
GET /api/stats
```
Returns platform-wide statistics (cached).
**Example:**
```bash
curl https://skills.palebluedot.live/api/stats
```
**Response:**
```json
{
"totalSkills": 178056,
"totalDownloads": 2053,
"totalCategories": 30,
"totalContributors": 9552
}
```
### Health Check
```
GET /api/health
```
Returns health status of all services (database, Redis, Meilisearch).
### Attribution
```
GET /api/attribution
```
Returns attribution data including license distribution and discovery sources.
---
## Error Responses
All endpoints return standard HTTP status codes:
| Status | Description |
|--------|-------------|
| `200` | Success |
| `400` | Bad request (missing/invalid parameters) |
| `401` | Authentication required |
| `403` | Forbidden |
| `404` | Not found |
| `429` | Rate limit exceeded |
| `500` | Internal server error |
Error response format:
```json
{
"error": "Description of the error"
}
```
---
## CLI Usage
The [SkillHub CLI](https://www.npmjs.com/package/skillhub) uses these APIs internally:
```bash
# Install CLI
npm install -g skillhub
# Search skills
npx skillhub search pdf
# Install a skill
npx skillhub install anthropics/skills/pdf
# List installed skills
npx skillhub list
```

View File

@@ -0,0 +1,196 @@
# SkillHub Manual Test Checklist
This checklist covers features NOT covered by automated E2E tests (43 Playwright tests).
Complete before production deployment.
## Already Automated - SKIP These
The following are covered by E2E tests in `apps/web/e2e/*.spec.ts`:
| Area | E2E Coverage |
|------|--------------|
| **Homepage** | Page load, stats, featured skills, navigation links |
| **Browse** | Skills list, search input, search filter, platform filter, skill cards, detail navigation |
| **Categories** | Page load, category list, names, skill counts, navigation |
| **Skill Detail** | Name, description, platforms, install command, GitHub link |
| **i18n** | EN default, language switcher, FA switch, RTL, Persian text, context preserve |
| **Mobile** | Mobile nav, menu toggle, single column, text size, touch targets |
| **API** | `/api/health`, `/api/stats`, `/api/categories`, `/api/skills/*`, `/api/skills/featured`, `/api/skills/recent` |
**To run E2E tests:** `pnpm --filter @skillhub/web test:e2e`
---
**Tester:** ________________
**Date:** ________________
**Environment:** [ ] Local Docker [ ] Staging [ ] Production
---
## Prerequisites
- [ ] All Docker containers running (`docker ps`)
- [ ] Database has data (`docker exec skillhub-db psql -U postgres -d skillhub -c "SELECT COUNT(*) FROM skills;"`)
- [ ] Health endpoint OK (`curl http://localhost:3000/api/health`)
- [ ] GitHub OAuth configured (for auth tests)
---
## 1. Authentication (GitHub OAuth)
### Sign In Flow
| Test | Expected | Pass |
|------|----------|------|
| Click "Sign In" in header | Redirects to GitHub OAuth | [ ] |
| Approve on GitHub | Returns to app, user dropdown visible | [ ] |
| User dropdown shows avatar + name | GitHub profile info displayed | [ ] |
| Refresh page while signed in | Session persists | [ ] |
### Sign Out Flow
| Test | Expected | Pass |
|------|----------|------|
| Click user dropdown → Sign Out | Logged out, "Sign In" button reappears | [ ] |
| Refresh after sign out | Still logged out | [ ] |
---
## 2. Favorites System (Auth Required)
### API Endpoints
| Test | Expected | Pass |
|------|----------|------|
| `GET /api/favorites` (unauthenticated) | 401 Unauthorized | [ ] |
| `GET /api/favorites` (authenticated) | Returns user's favorites array | [ ] |
| `POST /api/favorites` with `{skillId}` | 200, skill added | [ ] |
| `DELETE /api/favorites` with `{skillId}` | 200, skill removed | [ ] |
| `POST /api/favorites/check` with `{skillIds}` | Returns favorited status | [ ] |
### UI Tests
| Test | Expected | Pass |
|------|----------|------|
| Click heart on skill detail (unauthenticated) | Redirects to sign in | [ ] |
| Click heart on skill detail (authenticated) | Heart fills red, added to favorites | [ ] |
| Click filled heart | Heart unfills, removed from favorites | [ ] |
| Go to `/favorites` page | Shows all favorited skills | [ ] |
| Click heart on favorites page | Skill removed from list | [ ] |
| Empty favorites state | "No favorites" message + Browse button | [ ] |
---
## 3. Ratings System (Auth Required)
### API Endpoints
| Test | Expected | Pass |
|------|----------|------|
| `GET /api/ratings?skillId=...` | Returns ratings array + summary | [ ] |
| `POST /api/ratings` with `{skillId, rating: 5}` | 200, rating saved | [ ] |
| `POST /api/ratings` with invalid rating (0 or 6) | 400 error | [ ] |
| `GET /api/ratings/me?skillId=...` (authenticated) | Returns user's rating | [ ] |
### UI Tests
| Test | Expected | Pass |
|------|----------|------|
| Click stars (unauthenticated) | Redirects to sign in | [ ] |
| Click stars to rate (authenticated) | Stars fill, rating submitted | [ ] |
| Change rating | New rating saved, average updates | [ ] |
| Refresh page | Rating persists | [ ] |
---
## 4. Interactive UI Elements
### Browse Page Filters
| Test | Expected | Pass |
|------|----------|------|
| Sort dropdown (Stars/Downloads/Recent/Rating) | Results re-sorted, URL updates | [ ] |
| Verified checkbox | Only verified skills shown | [ ] |
| Combined filters (platform + sort + verified) | All filters applied together | [ ] |
| Load More button | Next page loads, more skills appear | [ ] |
### Skill Detail - Install Section
| Test | Expected | Pass |
|------|----------|------|
| Platform tabs (Claude/Codex/Copilot) | Command changes for each platform | [ ] |
| Copy button | Command copied to clipboard, checkmark appears | [ ] |
| Download ZIP button | Opens GitHub ZIP download in new tab | [ ] |
| Select Folder button (Chrome) | File picker opens, skill files created | [ ] |
---
## 5. Pages Not in E2E Tests
### Featured Page (`/featured`)
| Test | Expected | Pass |
|------|----------|------|
| Page loads | Shows top featured skills | [ ] |
| Skills sorted by popularity | Highest stars first | [ ] |
| Skill cards clickable | Navigate to detail page | [ ] |
### New Skills Page (`/new`)
| Test | Expected | Pass |
|------|----------|------|
| Page loads | Shows recently added skills | [ ] |
| Skills sorted by date | Most recent first | [ ] |
| Timestamps displayed | "X hours/days ago" format | [ ] |
### Favorites Page (`/favorites`)
| Test | Expected | Pass |
|------|----------|------|
| Unauthenticated access | Redirects to sign in | [ ] |
| Authenticated + no favorites | Empty state shown | [ ] |
| Authenticated + has favorites | Skills grid displayed | [ ] |
---
## 6. Error States
| Test | Expected | Pass |
|------|----------|------|
| Navigate to `/nonexistent-page` | 404 page displayed | [ ] |
| Navigate to `/skill/invalid/id` | Error or 404 shown | [ ] |
| API returns error | Graceful error message | [ ] |
---
## 7. Docker Health Checks
| Service | Command | Expected | Pass |
|---------|---------|----------|------|
| Web | `curl http://localhost:3000/api/health` | `status: ok` | [ ] |
| Database | `docker exec skillhub-db pg_isready` | Ready | [ ] |
| Redis | `docker exec skillhub-redis redis-cli ping` | PONG | [ ] |
| Meilisearch | `curl http://localhost:7700/health` | `available` | [ ] |
| Indexer | `docker logs skillhub-indexer --tail=5` | No errors | [ ] |
---
## 8. Real Data Verification
| Test | Expected | Pass |
|------|----------|------|
| Skills in database | `SELECT COUNT(*) FROM skills` > 0 | [ ] |
| Skills have real GitHub data | `github_stars`, `github_owner` populated | [ ] |
| Search returns results | Search for existing skill works | [ ] |
| Meilisearch synced | `curl localhost:7700/indexes/skills/stats` shows documents | [ ] |
---
## Notes
_Record any issues found:_
```
______________________________________________________________
______________________________________________________________
______________________________________________________________
```
---
## Sign-off
- [ ] All tests pass
- [ ] No blocking issues
- [ ] Ready for production
**Signature:** ________________ **Date:** ________________

View File

@@ -0,0 +1,93 @@
# Claim System
## Overview
The claim system allows users to:
1. **Add Skills**: Request to add their GitHub repositories containing SKILL.md files
2. **Remove Skills**: Request to remove their skills from SkillHub (auto-approved for verified owners)
## Add Skill Flow
### User Actions
1. Sign in with GitHub OAuth
2. Navigate to `/claim#add`
3. Enter GitHub repository URL
4. Optionally provide reason for adding the skill
5. Submit request
### Backend Processing
1. Validate GitHub URL format
2. Check if repository exists and is public
3. Scan repository for SKILL.md files (recursive auto-scan)
4. Create pending add request in database
5. Send confirmation email ONLY if skills found (`skillCount > 0`)
### Email Behavior
- **Skills found**: Email sent with count
- **No skills found**: No email sent (manual review needed, user notified on screen)
### Success States
- **1 skill found**: "SKILL.md was found and will be indexed soon"
- **Multiple skills**: "Found N skills that will be indexed soon"
- **No skills**: "No SKILL.md found - repository will be reviewed manually. You will receive an email if skills are approved for indexing."
### Field Requirements
- **Repository URL**: Required
- **Reason**: Optional (defaults to "No reason provided")
## Remove Skill Flow
### User Actions
1. Sign in with GitHub OAuth
2. Navigate to `/claim#remove`
3. Enter skill ID (format: `owner/repo/skill-name`)
4. Optionally provide reason for removal
5. Submit request
### Backend Processing
1. Verify skill exists in database
2. Verify user owns the repository (via GitHub API)
3. Auto-approve and block skill from indexing
4. Send confirmation email
### Field Requirements
- **Skill ID**: Required
- **Reason**: Optional (defaults to "No reason provided")
## Error Handling
| Error Code | Message | Trigger |
|------------|---------|---------|
| `INVALID_URL` | Invalid GitHub URL | Malformed URL |
| `INVALID_REPO` | Repository not found or not accessible | 404 from GitHub or private repo |
| `RATE_LIMIT_EXCEEDED` | GitHub API rate limit exceeded | 403 with rate limit header |
| `NETWORK_TIMEOUT` | Request timed out | Timeout during validation |
| `ALREADY_PENDING` | Pending request exists | Duplicate request |
| `NOT_OWNER` | Not repository owner | Ownership verification failed (remove only) |
| `SKILL_NOT_FOUND` | Skill not in database | Skill ID doesn't exist (remove only) |
| `GITHUB_ERROR` | GitHub verification failed | General GitHub API error |
| `INVALID_SKILL` | Invalid GitHub info | Skill missing repo data |
## Implementation Notes
### Key Features
- **Auto-scan**: Entire repository is scanned recursively for all SKILL.md files
- **No manual path input**: Removed confusing "Skill Path" field
- **Optional reasons**: Users don't have to explain their requests
- **Conditional emails**: Only send when skills are actually found
- **Bilingual**: Full support for English and Farsi (RTL)
- **Specific errors**: Each edge case has a clear, actionable error message
### Technical Details
- **API Routes**: `/api/skills/add-request` and `/api/skills/removal-request`
- **Component**: `ClaimForm.tsx` with tab-based UI
- **i18n**: Uses next-intl for translations
- **Email**: Resend library with bilingual templates
- **Security**: CSRF protection, GitHub OAuth verification
### Recent Improvements (Feb 2026)
- ✅ Fixed email logic to only send when skills found
- ✅ Made reason fields optional
- ✅ Removed confusing skill path field
- ✅ Improved success messages for different states
- ✅ Added specific error messages for rate limits, timeouts, and private repos

240
docs/self-hosting.md Normal file
View File

@@ -0,0 +1,240 @@
# Self-Hosting Guide
This guide explains how to deploy SkillHub on your own server.
## Prerequisites
- Docker and Docker Compose
- A server with at least 2GB RAM and 10GB disk space
- A domain name (optional, but recommended)
- GitHub Personal Access Token (for indexing skills)
## Quick Start
### 1. Clone the repository
```bash
git clone https://github.com/airano-ir/skillhub.git
cd skillhub
```
### 2. Configure environment
```bash
cp .env.example .env
```
Edit `.env` and set the following required variables:
```env
# Required: GitHub token for API access
GITHUB_TOKEN=ghp_your_token_here
# Required: Database password (change in production!)
POSTGRES_PASSWORD=your_secure_password
# Required: Meilisearch key (change in production!)
MEILI_MASTER_KEY=your_secure_key
# Optional: Your domain
NEXT_PUBLIC_APP_URL=https://skills.yourdomain.com
```
### 3. Start the services
```bash
# Development mode (without nginx)
docker compose up -d
# Production mode (with nginx reverse proxy)
docker compose --profile production up -d
```
### 4. Initialize the database
The database is automatically initialized when the container starts. You can verify by checking the logs:
```bash
docker compose logs db
```
### 5. Access the application
- Web interface: http://localhost:3000
- Meilisearch: http://localhost:7700
## Production Deployment
### SSL/TLS with Let's Encrypt
1. Install certbot:
```bash
apt install certbot
```
2. Generate certificates:
```bash
certbot certonly --standalone -d skills.yourdomain.com
```
3. Copy certificates to nginx directory:
```bash
mkdir -p nginx/ssl
cp /etc/letsencrypt/live/skills.yourdomain.com/fullchain.pem nginx/ssl/
cp /etc/letsencrypt/live/skills.yourdomain.com/privkey.pem nginx/ssl/
```
4. Update `nginx/nginx.conf` with your domain name.
5. Start with production profile:
```bash
docker compose --profile production up -d
```
### Auto-renewal
Add to crontab:
```bash
0 0 1 * * certbot renew --post-hook "docker compose restart nginx"
```
## Configuration Options
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://postgres:postgres@db:5432/skillhub` |
| `REDIS_URL` | Redis connection string | `redis://redis:6379` |
| `GITHUB_TOKEN` | GitHub PAT for API access | Required |
| `MEILI_MASTER_KEY` | Meilisearch master key | `skillhub-dev-key` |
| `INDEXER_CONCURRENCY` | Number of concurrent indexing jobs | `5` |
| `INDEXER_MIN_STARS` | Minimum GitHub stars for indexing | `2` |
### Scaling
#### Horizontal Scaling
You can run multiple instances of the web service behind a load balancer:
```bash
docker compose up -d --scale web=3
```
#### Resource Limits
Add resource limits in `docker-compose.yml`:
```yaml
services:
web:
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
```
## Maintenance
### Backup
```bash
# Backup PostgreSQL
docker compose exec db pg_dump -U postgres skillhub > backup.sql
# Backup Redis
docker compose exec redis redis-cli BGSAVE
# Copy Redis dump
docker cp skillhub-redis:/data/dump.rdb ./backup-redis.rdb
```
### Restore
```bash
# Restore PostgreSQL
cat backup.sql | docker compose exec -T db psql -U postgres skillhub
# Restore Redis
docker compose stop redis
docker cp ./backup-redis.rdb skillhub-redis:/data/dump.rdb
docker compose start redis
```
### Update
```bash
git pull origin main
docker compose build
docker compose up -d
```
### Logs
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f web
docker compose logs -f indexer
```
## Troubleshooting
### Database connection issues
```bash
# Check if database is healthy
docker compose exec db pg_isready -U postgres
# Check database logs
docker compose logs db
```
### Indexer not running
```bash
# Check indexer status
docker compose ps indexer
# View indexer logs
docker compose logs -f indexer
# Restart indexer
docker compose restart indexer
```
### Search not working
```bash
# Check Meilisearch status
curl http://localhost:7700/health
# View Meilisearch logs
docker compose logs meilisearch
```
## Security Recommendations
1. **Change default passwords** in `.env`
2. **Use HTTPS** in production
3. **Restrict database access** to internal network only
4. **Enable firewall** and only expose necessary ports
5. **Regular backups** to external storage
6. **Monitor logs** for suspicious activity
7. **Keep Docker and images updated**
## Support
- GitHub Issues: https://github.com/airano-ir/skillhub/issues
- Documentation: https://skills.palebluedot.live/docs