diff --git a/README.md b/README.md
index 4e8598f4..419c31f2 100644
--- a/README.md
+++ b/README.md
@@ -351,21 +351,52 @@ We have moved the full skill registry to a dedicated catalog to keep this README
### π Interactive Skills Web App
-You can now easily search, filter, and discover the perfect skills for your agent using our local Web App.
+A modern web interface to explore, search, and use the 950+ skills directly from your browser.
-To launch the app:
+#### β¨ Features
-1. Double-click the `START_APP.bat` file in the root directory (Windows) or run it from your terminal.
-2. The app will automatically configure everything and open in your default browser.
+- π **Full-text search** β Search skills by name, description, or content
+- π·οΈ **Category filters** β Frontend, Backend, Security, DevOps, etc.
+- π **Markdown rendering** β View complete documentation with syntax highlighting
+- π **Copy buttons** β Copy `@skill-name` or full content in 1 click
+- π οΈ **Prompt Builder** β Add custom context before copying
+- π **Dark mode** β Adaptive light/dark interface
+- β‘ **Auto-update** β Automatically syncs with upstream repo
-#### π οΈ New: Interactive Prompt Builder
+#### π Quick Start
-The web app is no longer just a static catalog! When you click on any skill, you will see an **Interactive Prompt Builder** box.
-Instead of manually copying `@skill-name` and writing your requirements separately in your IDE:
+**Windows:**
+```bash
+# Double-click or terminal
+START_APP.bat
+```
-1. Type your specific project constraints into the text box (e.g., "Use React 19 and Tailwind").
-2. Click **Copy Prompt**.
-3. Your clipboard now has a fully formatted, ready-to-run prompt combining the skill invocation and your custom context!
+**macOS/Linux:**
+```bash
+# 1. Install dependencies (first time)
+cd web-app && npm install
+
+# 2. Setup assets and launch
+npm run app:dev
+```
+
+**Available npm commands:**
+```bash
+npm run app:setup # Copy skills to web-app/public/
+npm run app:dev # Start dev server
+npm run app:build # Production build
+npm run app:preview # Preview production build
+```
+
+The app automatically opens at `http://localhost:5173` (or alternative port).
+
+#### π οΈ Prompt Builder
+
+On each skill page you'll find the **Prompt Builder**:
+1. Write specific requirements (e.g., "Use React 19, TypeScript and Tailwind")
+2. Click **Copy Prompt** β copies `@skill-name + context`
+3. Or **Copy Full Content** β copies the full documentation
+4. Paste into your AI assistant (Claude, Cursor, Gemini, etc.)
π **[View the Complete Skill Catalog (CATALOG.md)](CATALOG.md)**
diff --git a/web-app/index.html b/web-app/index.html
index 7645cc8c..a04b9def 100644
--- a/web-app/index.html
+++ b/web-app/index.html
@@ -4,7 +4,14 @@
-
web-app
+
+
+
+
+
+
+
+ Antigravity Skills | 950+ AI Agentic Skills Catalog
diff --git a/web-app/package-lock.json b/web-app/package-lock.json
index b20f3a63..d49b8f9d 100644
--- a/web-app/package-lock.json
+++ b/web-app/package-lock.json
@@ -11,11 +11,13 @@
"clsx": "^2.1.1",
"framer-motion": "^12.34.2",
"github-markdown-css": "^5.9.0",
+ "highlight.js": "^11.11.1",
"lucide-react": "^0.574.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.13.0",
+ "rehype-highlight": "^7.0.2",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
@@ -78,7 +80,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -1764,7 +1765,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1818,7 +1818,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1971,7 +1970,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -2332,7 +2330,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -2738,6 +2735,19 @@
"node": ">=8"
}
},
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
@@ -2765,6 +2775,22 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/hast-util-to-text": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+ "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unist-util-find-after": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-whitespace": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
@@ -2795,6 +2821,15 @@
"hermes-estree": "0.25.1"
}
},
+ "node_modules/highlight.js": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/html-url-attributes": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -3329,6 +3364,21 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/lowlight": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
+ "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.0.0",
+ "highlight.js": "~11.11.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4141,7 +4191,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -4169,7 +4218,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -4221,7 +4269,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4231,7 +4278,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -4314,6 +4360,23 @@
"react-dom": ">=18"
}
},
+ "node_modules/rehype-highlight": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz",
+ "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "lowlight": "^3.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-parse": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
@@ -4631,6 +4694,20 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-find-after": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+ "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/unist-util-is": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
@@ -4774,7 +4851,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -4896,7 +4972,6 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/web-app/package.json b/web-app/package.json
index efa74166..0d93533b 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -13,11 +13,13 @@
"clsx": "^2.1.1",
"framer-motion": "^12.34.2",
"github-markdown-css": "^5.9.0",
+ "highlight.js": "^11.11.1",
"lucide-react": "^0.574.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.13.0",
+ "rehype-highlight": "^7.0.2",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
diff --git a/web-app/src/pages/Home.jsx b/web-app/src/pages/Home.jsx
index 7cdd2ee7..85bbd0f0 100644
--- a/web-app/src/pages/Home.jsx
+++ b/web-app/src/pages/Home.jsx
@@ -10,6 +10,7 @@ export function Home() {
const [search, setSearch] = useState('');
const [categoryFilter, setCategoryFilter] = useState('all');
const [loading, setLoading] = useState(true);
+ const [displayCount, setDisplayCount] = useState(24); // Show 24 initially (6 rows of 4)
useEffect(() => {
fetch('/skills.json')
@@ -25,15 +26,39 @@ export function Home() {
});
}, []);
+ // Fuzzy search with scoring
+ const calculateScore = (skill, terms) => {
+ let score = 0;
+ const nameLower = skill.name.toLowerCase();
+ const descLower = (skill.description || '').toLowerCase();
+ const catLower = (skill.category || '').toLowerCase();
+
+ for (const term of terms) {
+ // Exact name match (highest priority)
+ if (nameLower === term) score += 100;
+ // Name starts with term
+ else if (nameLower.startsWith(term)) score += 50;
+ // Name contains term
+ else if (nameLower.includes(term)) score += 30;
+ // Category match
+ else if (catLower.includes(term)) score += 20;
+ // Description contains term
+ else if (descLower.includes(term)) score += 10;
+ }
+ return score;
+ };
+
useEffect(() => {
let result = skills;
if (search) {
- const lowerSearch = search.toLowerCase();
- result = result.filter(skill =>
- skill.name.toLowerCase().includes(lowerSearch) ||
- skill.description.toLowerCase().includes(lowerSearch)
- );
+ const terms = search.toLowerCase().trim().split(/\s+/).filter(t => t.length > 0);
+ if (terms.length > 0) {
+ result = result
+ .map(skill => ({ ...skill, _score: calculateScore(skill, terms) }))
+ .filter(skill => skill._score > 0)
+ .sort((a, b) => b._score - a._score);
+ }
}
if (categoryFilter !== 'all') {
@@ -43,6 +68,11 @@ export function Home() {
setFilteredSkills(result);
}, [search, categoryFilter, skills]);
+ // Reset display count when search/filter changes
+ useEffect(() => {
+ setDisplayCount(24);
+ }, [search, categoryFilter]);
+
const categories = ['all', ...new Set(skills.map(s => s.category).filter(Boolean))];
return (
@@ -50,7 +80,11 @@ export function Home() {
Explore Skills
-
Discover {skills.length} agentic capabilities for your AI assistant.
+
+ {search || categoryFilter !== 'all'
+ ? `Showing ${filteredSkills.length} of ${skills.length} skills`
+ : `Discover ${skills.length} agentic capabilities for your AI assistant.`}
+
@@ -59,11 +93,20 @@ export function Home() {
setSearch(e.target.value)}
/>
+ {search && (
+ setSearch('')}
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300"
+ title="Clear search"
+ >
+ Γ
+
+ )}
@@ -93,7 +136,7 @@ export function Home() {
Try adjusting your search or filter.
) : (
- filteredSkills.map((skill) => (
+ filteredSkills.slice(0, displayCount).map((skill) => (
+
+ {/* Load More Button */}
+ {!loading && filteredSkills.length > displayCount && (
+
+ setDisplayCount(prev => prev + 24)}
+ className="flex items-center space-x-2 px-6 py-3 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors shadow-sm"
+ >
+ Load More
+
+ ({filteredSkills.length - displayCount} remaining)
+
+
+
+ )}
);
diff --git a/web-app/src/pages/SkillDetail.jsx b/web-app/src/pages/SkillDetail.jsx
index ddc448db..8e5f5330 100644
--- a/web-app/src/pages/SkillDetail.jsx
+++ b/web-app/src/pages/SkillDetail.jsx
@@ -2,7 +2,9 @@
import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import Markdown from 'react-markdown';
+import rehypeHighlight from 'rehype-highlight';
import { ArrowLeft, Copy, Check, FileCode, AlertTriangle } from 'lucide-react';
+import 'highlight.js/styles/github-dark.css';
export function SkillDetail() {
const { id } = useParams();
@@ -157,8 +159,8 @@ export function SkillDetail() {
-
-
{content}
+
+ {content}