342 lines
13 KiB
Python
342 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""Seed the skills database with useful defaults"""
|
|
|
|
import httpx
|
|
|
|
BASE_URL = "http://helm:8675"
|
|
|
|
SKILLS = [
|
|
{
|
|
"id": "homelab-docker-compose",
|
|
"name": "Docker Compose Standard",
|
|
"category": "homelab",
|
|
"description": "Standard Docker Compose configuration patterns",
|
|
"content": """Always use docker-compose v3.8+. Include:
|
|
- health checks for all services
|
|
- restart: unless-stopped policy
|
|
- resource limits (memory, CPU)
|
|
- named volumes for persistent data
|
|
- .env file for secrets (never hardcode)
|
|
- explicit network definitions
|
|
- logging driver with size limits""",
|
|
"tags": ["docker", "compose", "infrastructure"]
|
|
},
|
|
{
|
|
"id": "homelab-traefik",
|
|
"name": "Traefik Reverse Proxy",
|
|
"category": "homelab",
|
|
"description": "Traefik configuration standards",
|
|
"content": """For Traefik reverse proxy setups:
|
|
- Use Docker provider with watched containers
|
|
- Enable ACME/Let's Encrypt for HTTPS
|
|
- Store certs in persistent volume
|
|
- Use middlewares for auth, rate limiting, redirects
|
|
- Label-based routing on containers
|
|
- Dashboard protected by auth middleware""",
|
|
"tags": ["traefik", "proxy", "https", "infrastructure"]
|
|
},
|
|
{
|
|
"id": "typescript-react",
|
|
"name": "React TypeScript Component",
|
|
"category": "coding",
|
|
"description": "Standard React component patterns with TypeScript",
|
|
"content": """React component standards:
|
|
- Use functional components with TypeScript interfaces
|
|
- Props defined as interface, not type alias
|
|
- Use React.FC only when children needed
|
|
- Prefer composition over inheritance
|
|
- Custom hooks for reusable logic
|
|
- Strict null checks enabled
|
|
- Avoid any, use unknown if needed""",
|
|
"tags": ["react", "typescript", "frontend"]
|
|
},
|
|
{
|
|
"id": "python-async",
|
|
"name": "Python Async Patterns",
|
|
"category": "coding",
|
|
"description": "Async/await best practices in Python",
|
|
"content": """Python async standards:
|
|
- Use async/await consistently, don't mix sync/async
|
|
- Use asyncio.gather() for concurrent operations
|
|
- Proper exception handling in async contexts
|
|
- Use async context managers (async with)
|
|
- Avoid blocking calls in async functions
|
|
- Use httpx over requests for async HTTP
|
|
- Timeout all async operations""",
|
|
"tags": ["python", "async", "asyncio"]
|
|
},
|
|
{
|
|
"id": "api-design",
|
|
"name": "REST API Design",
|
|
"category": "coding",
|
|
"description": "RESTful API design patterns",
|
|
"content": """API design standards:
|
|
- Use nouns for resources, not verbs
|
|
- Proper HTTP methods (GET/POST/PUT/DELETE)
|
|
- Return appropriate status codes
|
|
- Version APIs (/api/v1/)
|
|
- Use query params for filtering, sorting
|
|
- Pagination with limit/offset or cursor
|
|
- Consistent error response format
|
|
- Rate limiting headers""",
|
|
"tags": ["api", "rest", "backend"]
|
|
},
|
|
{
|
|
"id": "valheim-server",
|
|
"name": "Valheim Server Setup",
|
|
"category": "gameserver",
|
|
"description": "Valheim dedicated server configuration",
|
|
"content": """Valheim server standards:
|
|
- Run in Docker with persistent volumes
|
|
- Backup world files regularly
|
|
- Set -public 0 for private servers
|
|
- Configure admin list properly
|
|
- Monitor RAM usage (2-4GB typical)
|
|
- Use server sync for crossplay
|
|
- Restart nightly for memory leaks""",
|
|
"tags": ["valheim", "gaming", "docker"]
|
|
},
|
|
{
|
|
"id": "minecraft-server",
|
|
"name": "Minecraft Server Setup",
|
|
"category": "gameserver",
|
|
"description": "Minecraft server configuration patterns",
|
|
"content": """Minecraft server standards:
|
|
- Use PaperMC for performance
|
|
- Pre-generate world chunks
|
|
- Configure view-distance appropriately (6-10)
|
|
- Use Aikar's flags for JVM optimization
|
|
- Regular backups with rotation
|
|
- Whitelist for private servers
|
|
- Monitor TPS and chunk loading""",
|
|
"tags": ["minecraft", "gaming", "java"]
|
|
},
|
|
{
|
|
"id": "git-commits",
|
|
"name": "Git Commit Standards",
|
|
"category": "coding",
|
|
"description": "Commit message conventions",
|
|
"content": """Commit message format:
|
|
- Conventional Commits (feat:, fix:, chore:, etc.)
|
|
- Imperative mood ("add feature" not "added feature")
|
|
- First line max 50 chars
|
|
- Blank line before body
|
|
- Body wraps at 72 chars
|
|
- Reference issues/PRs when applicable""",
|
|
"tags": ["git", "workflow", "documentation"]
|
|
},
|
|
{
|
|
"id": "dnd-npc-creation",
|
|
"name": "D&D NPC Creation",
|
|
"category": "dnd",
|
|
"description": "Standards for creating memorable non-player characters",
|
|
"content": """NPC creation guidelines:
|
|
- Give each NPC one distinctive trait (speech pattern, habit, appearance)
|
|
- Motivation > backstory - what do they want NOW?
|
|
- Tie NPCs to locations or other NPCs (web of connections)
|
|
- Use the "Three Details" rule: name, appearance, mannerism
|
|
- Avoid stereotypes; subvert expectations thoughtfully
|
|
- Consider how they change over time (arcs aren't just for PCs)""",
|
|
"tags": ["dnd", "npc", "character", "writing"]
|
|
},
|
|
{
|
|
"id": "dnd-plot-hooks",
|
|
"name": "D&D Plot Hook Generation",
|
|
"category": "dnd",
|
|
"description": "Patterns for compelling quest seeds and story hooks",
|
|
"content": """Effective plot hooks include:
|
|
- Personal connection to a PC's backstory
|
|
- Urgent need (timer = engagement)
|
|
- Moral ambiguity (not just "kill monsters")
|
|
- Mystery with multiple potential solutions
|
|
- Hook should lead to 3+ possible directions
|
|
- Include a "weird" element to spark curiosity
|
|
- Avoid railroading; present options, not one path""",
|
|
"tags": ["dnd", "plot", "quest", "writing"]
|
|
},
|
|
{
|
|
"id": "homelab-backup-strategy",
|
|
"name": "Home Lab Backup Standards",
|
|
"category": "homelab",
|
|
"description": "Reliable backup patterns for self-hosted services",
|
|
"content": """Backup best practices:
|
|
- 3-2-1 rule: 3 copies, 2 media types, 1 offsite
|
|
- Use Borg/Restic with deduplication and encryption
|
|
- Test restores quarterly (backup is worthless without verification)
|
|
- Backup databases with point-in-time recovery (WAL for Postgres)
|
|
- Store backups on different physical disks than production
|
|
- Automate with systemd timers or cron, monitor failures
|
|
- Document restore procedures in runbooks""",
|
|
"tags": ["backup", "borg", "restic", "disaster-recovery"]
|
|
},
|
|
{
|
|
"id": "homelab-monitoring",
|
|
"name": "Home Lab Monitoring Stack",
|
|
"category": "homelab",
|
|
"description": "Prometheus + Grafana + Alertmanager setup patterns",
|
|
"content": """Monitoring standards:
|
|
- Prometheus scrapes metrics from all services (expose /metrics endpoint)
|
|
- Grafana dashboards for: system resources, app metrics, business KPIs
|
|
- Alertmanager with tiered alerts: info/warning/critical
|
|
- Use node_exporter for host metrics, docker_exporter for containers
|
|
- Retention: 30 days for warnings, 90 days for critical, 1 year for compliance
|
|
- Set up blackbox exporters for external uptime monitoring
|
|
- Document runbooks for each critical alert""",
|
|
"tags": ["monitoring", "prometheus", "grafana", "observability"]
|
|
},
|
|
{
|
|
"id": "python-testing-pytest",
|
|
"name": "Python Testing with pytest",
|
|
"category": "coding",
|
|
"description": "Comprehensive pytest patterns and practices",
|
|
"content": """Testing standards:
|
|
- Use pytest fixtures with function scope for isolation
|
|
- Test one behavior per test function (single responsibility)
|
|
- Use descriptive test names that explain the expectation
|
|
- Mock external services (HTTP, DB) with pytest-mock
|
|
- Parameterize tests for multiple input combinations
|
|
- Aim for 80%+ coverage, but prioritize critical paths
|
|
- Use hypothesis for property-based testing on complex logic""",
|
|
"tags": ["python", "testing", "pytest", "tdd"]
|
|
},
|
|
{
|
|
"id": "docker-security",
|
|
"name": "Docker Security Hardening",
|
|
"category": "security",
|
|
"description": "Security best practices for containerized applications",
|
|
"content": """Docker security checklist:
|
|
- Use distroless or alpine base images (minimal attack surface)
|
|
- Run as non-root user (USER directive in Dockerfile)
|
|
- Scan images with trivy or grype in CI
|
|
- Use read-only filesystems where possible (volumes for writes)
|
|
- Drop capabilities you don't need (--cap-drop ALL, then add back)
|
|
- Never store secrets in images - use Docker secrets or env files
|
|
- Keep base images updated (automate with Renovate/Dependabot)""",
|
|
"tags": ["docker", "security", "hardening"]
|
|
},
|
|
{
|
|
"id": "project-setup-guide",
|
|
"name": "AI Skills Project Setup",
|
|
"category": "onboarding",
|
|
"description": "How to initialize a project with the AI Skills API and agentic-templates",
|
|
"content": """If you're starting a new project or integrating with AI Skills:
|
|
|
|
1. **Ensure the stack is running** on helm:
|
|
- docker compose -f /opt/ai-skills-api/docker-compose.yml up -d
|
|
|
|
2. **Initialize your project as a git repo** (if not already):
|
|
```bash
|
|
git init
|
|
git remote add origin <your-remote-url>
|
|
```
|
|
|
|
3. **Connect your AI client** (OpenCode, Claude Desktop) to the MCP server at `http://helm:3000`. Run the setup script:
|
|
```bash
|
|
git clone git.bouncypixel.com:helm/agentic-templates.git
|
|
cd agentic-templates
|
|
python setup-mcp.py
|
|
```
|
|
|
|
4. **Project identifier**: The system uses your git remote origin URL as the stable project identifier (e.g., `https://github.com/username/repo.git`). This ensures your conventions and memories follow you across machines. If you're not in a git repo, you can manually set a project name, but it won't sync across machines.
|
|
|
|
5. **Start coding**: OpenCode will automatically connect to the skills. Tell the AI your project identifier when prompted (or it will auto-detect if git remote exists).
|
|
|
|
6. **Seed skills**: Run `python examples/seed-data.py` on helm to populate default skills.
|
|
|
|
The AI will help you build a knowledge base that compounds over time. It will ask before creating new skills or memories.""",
|
|
"tags": ["setup", "onboarding", "git", "mcp"]
|
|
}
|
|
]
|
|
|
|
CONVENTIONS = [
|
|
{
|
|
"id": "home-server-conventions",
|
|
"project_path": "/opt/home-server",
|
|
"name": "Home Server Standards",
|
|
"content": """All home server deployments:
|
|
- Docker Compose for all services
|
|
- Traefik for reverse proxy
|
|
- Health checks on all containers
|
|
- Centralized logging
|
|
- Automated backups
|
|
- Resource limits defined""",
|
|
"auto_inject": True
|
|
}
|
|
]
|
|
|
|
SNIPPETS = [
|
|
{
|
|
"id": "docker-healthcheck",
|
|
"name": "Docker Health Check Template",
|
|
"language": "yaml",
|
|
"category": "docker",
|
|
"content": """healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 40s""",
|
|
"tags": ["docker", "health", "template"]
|
|
},
|
|
{
|
|
"id": "traefik-labels",
|
|
"name": "Traefik Docker Labels",
|
|
"language": "yaml",
|
|
"category": "docker",
|
|
"content": """labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.myapp.rule=Host(`myapp.example.com`)"
|
|
- "traefik.http.routers.myapp.entrypoints=websecure"
|
|
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.myapp.loadbalancer.server.port=8080""",
|
|
"tags": ["traefik", "labels", "template"]
|
|
}
|
|
]
|
|
|
|
|
|
def seed():
|
|
with httpx.Client(timeout=30.0) as client:
|
|
print("Seeding skills...")
|
|
for skill in SKILLS:
|
|
try:
|
|
response = client.post(f"{BASE_URL}/skills", json=skill)
|
|
if response.status_code == 200:
|
|
print(f" ✓ {skill['id']}")
|
|
elif response.status_code == 400:
|
|
print(f" ~ {skill['id']} (already exists)")
|
|
else:
|
|
print(f" ✗ {skill['id']}: {response.text}")
|
|
except Exception as e:
|
|
print(f" ✗ {skill['id']}: {e}")
|
|
|
|
print("\nSeeding conventions...")
|
|
for convention in CONVENTIONS:
|
|
try:
|
|
response = client.post(f"{BASE_URL}/conventions", json=convention)
|
|
if response.status_code == 200:
|
|
print(f" ✓ {convention['id']}")
|
|
elif response.status_code == 400:
|
|
print(f" ~ {convention['id']} (already exists)")
|
|
else:
|
|
print(f" ✗ {convention['id']}: {response.text}")
|
|
except Exception as e:
|
|
print(f" ✗ {convention['id']}: {e}")
|
|
|
|
print("\nSeeding snippets...")
|
|
for snippet in SNIPPETS:
|
|
try:
|
|
response = client.post(f"{BASE_URL}/snippets", json=snippet)
|
|
if response.status_code == 200:
|
|
print(f" ✓ {snippet['id']}")
|
|
elif response.status_code == 400:
|
|
print(f" ~ {snippet['id']} (already exists)")
|
|
else:
|
|
print(f" ✗ {snippet['id']}: {response.text}")
|
|
except Exception as e:
|
|
print(f" ✗ {snippet['id']}: {e}")
|
|
|
|
print("\nDone! Check http://helm:8675/docs")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
seed()
|