import { useState, useEffect, useMemo } from 'react'; import { Search, Filter, AlertCircle, RefreshCw, ArrowUpDown } from 'lucide-react'; import { VirtuosoGrid } from 'react-virtuoso'; import { useSkills } from '../context/SkillContext'; import { SkillCard } from '../components/SkillCard'; import type { SyncMessage, CategoryStats } from '../types'; import { usePageMeta } from '../hooks/usePageMeta'; import { APP_HOME_CATALOG_COUNT, buildHomeMeta } from '../utils/seo'; export function Home(): React.ReactElement { const { skills, stars, loading, error, refreshSkills } = useSkills(); const [search, setSearch] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [categoryFilter, setCategoryFilter] = useState('all'); const [sortBy, setSortBy] = useState('default'); const [syncing, setSyncing] = useState(false); const [syncMsg, setSyncMsg] = useState(null); const [commandCopied, setCommandCopied] = useState(false); const installCommand = 'npx antigravity-awesome-skills'; const docsLink = 'https://github.com/sickn33/antigravity-awesome-skills/blob/main/docs/users/usage.md'; const installLink = 'https://www.npmjs.com/package/antigravity-awesome-skills'; usePageMeta(buildHomeMeta(skills.length)); const copyInstallCommand = async () => { await navigator.clipboard.writeText(installCommand); setCommandCopied(true); window.setTimeout(() => setCommandCopied(false), 2000); }; useEffect(() => { const timeoutId = window.setTimeout(() => { setDebouncedSearch(search); }, 300); return () => { window.clearTimeout(timeoutId); }; }, [search]); const filteredSkills = useMemo(() => { let result = [...skills]; if (debouncedSearch) { const lowerSearch = debouncedSearch.toLowerCase(); result = result.filter(skill => skill.name.toLowerCase().includes(lowerSearch) || skill.description.toLowerCase().includes(lowerSearch) ); } if (categoryFilter !== 'all') { result = result.filter(skill => skill.category === categoryFilter); } // Apply sorting if (sortBy === 'stars') { result = [...result].sort((a, b) => (stars[b.id] || 0) - (stars[a.id] || 0)); } else if (sortBy === 'newest') { result = [...result].sort((a, b) => (b.date_added || '').localeCompare(a.date_added || '')); } else if (sortBy === 'az') { result = [...result].sort((a, b) => a.name.localeCompare(b.name)); } return result; }, [debouncedSearch, categoryFilter, sortBy, skills, stars]); // Sort categories by count (most skills first), with 'uncategorized' at the end const { categories, categoryStats } = useMemo(() => { const stats: CategoryStats = {}; skills.forEach(skill => { stats[skill.category] = (stats[skill.category] || 0) + 1; }); const cats = ['all', ...Object.keys(stats) .filter(cat => cat !== 'uncategorized') .sort((a, b) => stats[b] - stats[a]), ...(stats['uncategorized'] ? ['uncategorized'] : []) ]; return { categories: cats, categoryStats: stats }; }, [skills]); const handleSync = async () => { setSyncing(true); setSyncMsg(null); try { const res = await fetch('/api/refresh-skills', { method: 'POST' }); const data = await res.json(); if (data.success) { if (data.upToDate) { setSyncMsg({ type: 'info', text: 'ℹ️ Skills are already up to date!' }); } else { setSyncMsg({ type: 'success', text: `✅ Synced ${data.count} skills!` }); await refreshSkills(); } } else { setSyncMsg({ type: 'error', text: `❌ ${data.error}` }); } } catch { setSyncMsg({ type: 'error', text: '❌ Network error' }); } finally { setSyncing(false); setTimeout(() => setSyncMsg(null), 5000); } }; return (

Take action

Discover, install, and use trusted AI skills in minutes

Antigravity Awesome Skills is a discoverable catalog of installable capabilities for AI assistants. Install once, then test the highest-value skill directly from your terminal without waiting for documentation hops. Search, filter, then copy a ready-to-run prompt in one pass.

Install with npm Read getting started docs

Recommended command: {installCommand}

Explore Skills

Discover {Math.max(skills.length, APP_HOME_CATALOG_COUNT)}+ agentic capabilities for your AI assistant.

{syncMsg && ( {syncMsg.text} )}
setSearch(e.target.value)} />
{loading ? (
{[...Array(8)].map((_, i) => (
))}
) : error && skills.length === 0 ? (

Unable to load skills

{error}

) : filteredSkills.length === 0 ? (

No skills found

Try adjusting your search or filter.

) : ( { const skill = filteredSkills[index]; return ; }} /> )}
); } export default Home;