import React, { useState, useEffect } from 'react'; import { Search as _Search, Filter as _Filter, Package, Sparkles, FileText, GitFork } from 'lucide-react'; import { SkillCard } from '../components/SkillCard'; import { Footer } from '../components/Footer'; import type { SkillMetadata, SkillsIndex } from '../types'; const SKILLS_INDEX_PATH = '/skills/index.json'; const isProbablyHtmlDocument = (text: string): boolean => { const start = text.trimStart().slice(0, 200).toLowerCase(); return start.startsWith(' { try { const parsed = JSON.parse(raw) as Partial | null; if (!parsed || !Array.isArray(parsed.skills)) return null; return { version: typeof parsed.version === 'string' ? parsed.version : '1.0.0', updated: typeof parsed.updated === 'string' ? parsed.updated : '', skills: parsed.skills as SkillMetadata[], }; } catch { return null; } }; export const SkillsCatalog: React.FC = () => { const [skills, setSkills] = useState([]); const [filteredSkills, setFilteredSkills] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchTerm, _setSearchTerm] = useState(''); const [categoryFilter, _setCategoryFilter] = useState('all'); useEffect(() => { const fetchSkills = async () => { try { const response = await fetch(SKILLS_INDEX_PATH, { headers: { Accept: 'application/json' }, }); // Missing index file is a valid "empty catalog" state. if (response.status === 404) { setSkills([]); setFilteredSkills([]); return; } if (!response.ok) { throw new Error('Failed to fetch skills index'); } const contentType = response.headers.get('content-type') ?? ''; const raw = await response.text(); // Some SPA setups return index.html with 200 for missing JSON files. if (!raw.trim() || contentType.includes('text/html') || isProbablyHtmlDocument(raw)) { setSkills([]); setFilteredSkills([]); return; } const data = parseSkillsIndex(raw); if (!data) { throw new Error('Invalid skills index format'); } setSkills(data.skills); setFilteredSkills(data.skills); } catch (err) { console.error('Failed to load skills index:', err); setError('Failed to load skills catalog'); } finally { setLoading(false); } }; fetchSkills(); }, []); useEffect(() => { let result = skills; // Apply search filter if (searchTerm) { const term = searchTerm.toLowerCase(); result = result.filter( (skill) => skill.name.toLowerCase().includes(term) || skill.description.toLowerCase().includes(term) ); } // Apply category filter if (categoryFilter !== 'all') { result = result.filter((skill) => skill.category === categoryFilter); } setFilteredSkills(result); }, [searchTerm, categoryFilter, skills]); // Get unique categories from skills (used in commented filter UI) const _categories = ['all', ...new Set(skills.map((s) => s.category).filter(Boolean))]; if (loading) { return (

Loading skills...

); } if (error) { return (

No Skills Available

{error}

Skills will appear here after the first skill release.

); } return (
{/* Header */}

Skills Catalog

Browse security skills for your AI agents. Each skill is verified for safety and distributed with checksums for integrity verification.

{/* Filters - Hidden for now, uncomment when needed
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2.5 bg-clawd-800 border border-clawd-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-clawd-accent" />
*/} {/* Skills Grid */} {filteredSkills.length > 0 ? (
{filteredSkills.map((skill) => ( ))}
) : (

No skills found

{searchTerm || categoryFilter !== 'all' ? 'Try adjusting your filters' : 'No skills have been released yet'}

)} {/* Stats */} {skills.length > 0 && (
Showing {filteredSkills.length} of {skills.length} skills
)} {/* Shoutout */}

Contribute Security Skills

SKILLS-BASED

Humans & agents: submit skills that make bots safer (prompt injection defenses, drift checks, tool hardening, policy enforcement). We’ll package them with checksums so everyone can verify integrity.

📄

Read CONTRIBUTING.md

Guidelines for authoring, packaging, and releasing skills to the ClawSec catalog.

🍴

Fork the repository

Start a contribution branch and open a PR with your new security skill.

); };