
Find Skills
- 192 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Discover and install relevant agent skills when starting a task or extending agent capabilities for a new workflow.
About
Helps users discover and install agent skills for new tasks: search skill catalogs, match capabilities to goals, recommend installations, and extend agent workflows without manually browsing repositories.
- Skill discovery
- Capability matching
- Install guidance
- Workflow extension
- Agent tooling
Find Skills by the numbers
- 192 all-time installs (skills.sh)
- +6 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #188 of 782 Skill Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill find-skillsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 192 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Discover and install relevant agent skills when starting a task or extending agent capabilities for a new workflow.
Files
Find Skills
Overview
Searches the open agent skills ecosystem and installs matching skills via the Skills CLI (pnpm dlx skills). Includes an enrichment script that fetches descriptions from skills.sh for each result, giving users context beyond raw skill names.
When to use: User asks for help with a domain that might have existing skills, wants to browse available skills, or asks to extend agent capabilities.
When NOT to use: User already knows the exact skill and install command, or the task has nothing to do with skill discovery.
Quick Reference
| Action | Command | Notes |
|---|---|---|
| Search skills | pnpm dlx skills find [query] | Interactive or keyword search |
| Enriched search | node scripts/enrich_find.js "query" | Adds descriptions from skills.sh |
| Install skill | pnpm dlx skills add <source> -s <name> -a claude-code -y | Default to Claude Code agent |
| Install globally | pnpm dlx skills add <source> -s <name> -a claude-code -g -y | User-level install |
| Install all skills | pnpm dlx skills add <source> --all | All skills, all agents, skip prompts |
| Multi-agent install | pnpm dlx skills add <source> -s <name> -a claude-code opencode github-copilot | Target multiple agents |
| List repo skills | pnpm dlx skills add <source> --list | Preview without installing |
| List installed | pnpm dlx skills list | Project-level; add -g for global |
| Remove skill | pnpm dlx skills remove <name> | Interactive if no name given |
| Check updates | pnpm dlx skills check | Shows available updates |
| Update skills | pnpm dlx skills update | Updates all installed |
| Init new skill | pnpm dlx skills init <name> | Scaffolds SKILL.md |
| Browse online | https://skills.sh/ | Web catalog |
Common Skill Categories
| Category | Example Queries |
|---|---|
| Web Development | react, nextjs, typescript, css, tailwind |
| Testing | testing, jest, playwright, e2e |
| DevOps | deploy, docker, kubernetes, ci-cd |
| Documentation | docs, readme, changelog, api-docs |
| Code Quality | review, lint, refactor, best-practices |
| Design | ui, ux, design-system, accessibility |
| Productivity | workflow, automation, git |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
Using npx skills | Use pnpm dlx skills for consistent package management |
Forgetting -y on install | Add -y to skip interactive confirmation prompts |
| Vague search terms like "help" | Use specific keywords: "react testing", "pr review" |
| Not checking skills.sh first | Browse https://skills.sh/ for curated listings |
| Installing without reviewing | Check the skill page on skills.sh before installing |
Installing without -a claude-code | Always specify agent: -a claude-code (or target agent) |
Using owner/repo for private repos | Use SSH URL: git@github.com:Org/repo.git |
| Running enrichment without Node.js | Script requires Node.js with https module (built-in) |
Delegation
- Skill search execution: Run
pnpm dlx skills findor the enrichment script directly - Skill installation: Run
pnpm dlx skills addafter user confirms - Detailed skill info: Browse the skill page on skills.sh before recommending
If the skill-management skill is available, delegate skill creation, auditing, and validation to it.Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s skill-management -a claude-code -yIf no matching skill is found, offer to help directly and suggest pnpm dlx skills init to create a custom skill.
References
- Discovery workflow and search strategies
- Enriched search script usage and options
- Installing skills from private repositories
Discovery Guide
When to Activate
Activate skill discovery when the user:
- Asks "how do I do X" where X might have an existing skill
- Says "find a skill for X" or "is there a skill for X"
- Asks "can you do X" where X is a specialized capability
- Expresses interest in extending agent capabilities
- Wants to search for tools, templates, or workflows
- Mentions they wish they had help with a specific domain
Step-by-Step Workflow
Step 1: Understand What They Need
Identify three things before searching:
1. The domain — React, testing, design, deployment, etc. 2. The specific task — writing tests, creating animations, reviewing PRs 3. Likelihood a skill exists — common tasks in popular domains usually have skills
Step 2: Search for Skills
Run the find command with a relevant query:
pnpm dlx skills find [query]Example mappings from user requests to search queries:
| User Says | Search Query |
|---|---|
| "How do I make my React app faster?" | pnpm dlx skills find react performance |
| "Can you help me with PR reviews?" | pnpm dlx skills find pr review |
| "I need to create a changelog" | pnpm dlx skills find changelog |
| "Help me write better tests" | pnpm dlx skills find testing |
| "I want to set up CI/CD" | pnpm dlx skills find ci-cd deploy |
For richer results with descriptions, use the enrichment script:
node scripts/enrich_find.js "react performance"Step 3: Present Options to the User
When results are found, present each skill with:
1. The skill name and what it does 2. The install command (defaulting to Claude Code as the target agent) 3. A link to learn more on skills.sh
Example response format:
I found a skill that might help! The "react-best-practices" skill provides
React performance optimization guidelines.
To install it:
pnpm dlx skills add vercel-labs/agent-skills -s react-best-practices -a claude-code -y
Learn more: https://skills.sh/vercel-labs/agent-skills/react-best-practicesStep 4: Offer to Install
If the user wants to proceed, install with confirmation bypass. Default to Claude Code as the target agent:
pnpm dlx skills add <source> -s <name> -a claude-code -yFor global (user-level) install:
pnpm dlx skills add <source> -s <name> -a claude-code -g -y-a claude-codetargets Claude Code specifically-ginstalls at user level (available across projects)-yskips interactive confirmation
Agent Targeting
The -a flag controls which agent(s) receive the skill. Always specify the target agent explicitly.
Default agent: claude-code
To install for multiple agents, pass multiple names:
pnpm dlx skills add <source> -s <name> -a claude-code opencode github-copilot -yTo install for all detected agents:
pnpm dlx skills add <source> -s <name> -a '*' -yCommon agent identifiers (full list at github.com/vercel-labs/skills):
| Agent | CLI Name |
|---|---|
| Claude Code | claude-code |
| OpenCode | opencode |
| GitHub Copilot | github-copilot |
| Cursor | cursor |
| Windsurf | windsurf |
| Gemini CLI | gemini-cli |
| Codex | codex |
| Roo Code | roo |
| Cline | cline |
| Goose | goose |
| Amp | amp |
CLI Reference
Commands
| Command | Description |
|---|---|
skills add <source> | Install skills from a source |
skills remove | Remove installed skills |
skills list | List installed skills |
skills find [query] | Search for skills interactively |
skills init [name] | Scaffold a new skill |
skills check | Check for available updates |
skills update | Update all installed skills |
Add Options
| Flag | Short | Description |
|---|---|---|
--global | -g | Install at user level instead of project |
--agent <agents> | -a | Target specific agents (default: all detected) |
--skill <skills> | -s | Install specific skills from the source |
--list | -l | List available skills without installing |
--yes | -y | Skip confirmation prompts |
--all | Shorthand for -s '*' -a '*' -y | |
--full-depth | Search all subdirectories for skills |
Remove Options
| Flag | Short | Description |
|---|---|---|
--global | -g | Remove from global scope |
--agent <agents> | -a | Remove from specific agents |
--skill <skills> | -s | Remove specific skills |
--yes | -y | Skip confirmation prompts |
--all | Shorthand for -s '*' -a '*' -y |
List Options
| Flag | Short | Description |
|---|---|---|
--global | -g | List global skills (default: project) |
--agent <agents> | -a | Filter by specific agents |
Source Formats
The <source> argument accepts multiple formats:
| Format | Example |
|---|---|
| GitHub shorthand | vercel-labs/agent-skills |
| GitHub URL | https://github.com/owner/repo |
| GitLab URL | https://gitlab.com/owner/repo |
| SSH URL (private repos) | git@github.com:Org/repo.git |
| Any git URL | https://example.com/repo.git |
| Local path | ./my-skills or /absolute/path |
Search Tips
1. Use specific keywords — "react testing" beats just "testing" 2. Try alternative terms — if "deploy" returns nothing, try "deployment" or "ci-cd" 3. Check popular sources — many skills come from vercel-labs/agent-skills or oakoss/agent-skills 4. Browse the catalog — https://skills.sh/ shows curated listings 5. Combine domain + task — "typescript validation", "nextjs auth", "react forms" 6. Preview before installing — use --list to see available skills in a repo
When No Skills Are Found
If no relevant skills exist:
1. Acknowledge that no existing skill was found 2. Offer to help with the task directly using general capabilities 3. Suggest creating a custom skill if the task is recurring
I searched for skills related to "xyz" but didn't find any matches.
I can still help you with this task directly. Would you like me to proceed?
If this is something you do often, you could create your own skill:
pnpm dlx skills init my-xyz-skillSkill Sources
| Source | Description |
|---|---|
skills.sh | Web catalog with descriptions and install links |
agent-skills.md | Fallback documentation source |
| GitHub repos | Skills are hosted in GitHub repositories |
Skills follow the Agent Skills open standard and work across 27+ agents including Claude Code, Cursor, Gemini CLI, and others.
Enriched Search
What It Does
The enrich_find.js script wraps pnpm dlx skills find and enriches each result with a human-readable description fetched from skills.sh. This gives users context about what each skill does without having to visit every page manually.
How It Works
The script operates in three steps:
1. Search — Runs pnpm dlx skills find <query> and parses the output 2. Fetch — For each result, fetches the skills.sh page and extracts descriptive text 3. Display — Presents results with name, URL, and description in a formatted layout
Usage
node scripts/enrich_find.js "<query>"Options
| Flag | Default | Description |
|---|---|---|
--max N | 10 | Maximum number of results to enrich |
--timeout N | 10 | Per-request timeout in seconds |
--concurrency N | 5 | Number of parallel description fetches |
--no-fetch | false | Show matches without fetching descriptions |
Examples
# Basic search
node scripts/enrich_find.js "react"
# Limit to 5 results
node scripts/enrich_find.js "testing" --max 5
# Fast mode — skip description fetching
node scripts/enrich_find.js "deploy" --no-fetch
# Increase timeout for slow connections
node scripts/enrich_find.js "kubernetes" --timeout 20
# Single-threaded fetching
node scripts/enrich_find.js "auth" --concurrency 1Output Format
Each result is displayed as:
owner/repo@skill
└ https://skills.sh/owner/repo/skill
description text from the skill pageWith ANSI formatting:
- Bold — skill name
- Blue — URL
- Gray — description
When no results are found, outputs: No skills found.
Description Sources
The script tries two sources for descriptions, in order:
1. skills.sh — Extracts the first <p> tag from the prose section, or falls back to the <meta name="description"> tag 2. agent-skills.md — If skills.sh has no description, tries the equivalent page on agent-skills.md
If neither source provides a description, displays [no description found].
Requirements
- Node.js — Uses built-in
httpsandchild_processmodules (no npm dependencies) - Network access — Fetches from skills.sh and agent-skills.md
- Skills CLI —
pnpm dlx skillsmust be available (installed on demand via pnpm)
Error Handling
- Network timeouts are handled per-request without failing the entire batch
- HTTP errors (4xx, 5xx) fall through to the fallback source
- Redirect chains are followed up to 3 hops
- If the skills CLI is not available, the script exits with a non-zero status
- Invalid options produce a usage error message
Private Repositories
The Problem
The owner/repo shorthand converts to an HTTPS URL internally. For private repos, this triggers the system credential helper, which often results in an endless spinner (the git credential prompt is hidden) or a "Repository not found" error.
Recommended: Use SSH URLs
The most reliable method for private repos is passing an SSH URL directly:
# GitHub private repo — install for Claude Code
pnpm dlx skills add git@github.com:YourOrg/private-skills.git -a claude-code -y
# GitHub private repo — specific skill
pnpm dlx skills add git@github.com:YourOrg/private-skills.git -s my-skill -a claude-code -y
# GitHub private repo — global install
pnpm dlx skills add git@github.com:YourOrg/private-skills.git -s my-skill -a claude-code -g -y
# GitLab private repo
pnpm dlx skills add git@gitlab.com:YourOrg/private-skills.git -a claude-code -y
# Multiple agents at once
pnpm dlx skills add git@github.com:YourOrg/private-skills.git -s my-skill -a claude-code opencode github-copilot -y
# List available skills in a private repo
pnpm dlx skills add git@github.com:YourOrg/private-skills.git --listThis works because SSH uses existing key-based authentication rather than HTTPS credentials.
Prerequisites
The user must have SSH keys configured for their git host:
# Verify SSH access to GitHub
ssh -T git@github.com
# Verify SSH access to GitLab
ssh -T git@gitlab.comIf SSH is not configured, the user needs to set up an SSH key and add it to their git host account.
Alternative: Configure HTTPS Credentials
If SSH is not available, pre-configure git credentials before running the skills CLI:
# Option 1: Use gh CLI (recommended if available)
gh auth setup-git
# Option 2: Store a personal access token
git config --global credential.helper store
# Then authenticate once — credentials are cached for future useAfter configuring credentials, the owner/repo shorthand works normally:
pnpm dlx skills add YourOrg/private-skills -s my-skill -a claude-code -yKnown Limitations
skills check and skills update Do Not Work
The CLI makes unauthenticated GitHub API calls to detect skill updates. For private repos, these return 404, so:
pnpm dlx skills checkalways shows "skipped (reinstall needed)"pnpm dlx skills updatecannot update private repo skills
Workaround: Reinstall the skill to update it:
pnpm dlx skills add git@github.com:YourOrg/private-skills.git -s my-skill -a claude-code -ySpinner Hides Credential Prompts
If the CLI appears stuck during "Cloning repository," it may be waiting for credential input hidden behind the spinner. Kill the process and retry with an SSH URL instead.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Endless spinner on clone | Credential prompt hidden behind spinner | Use SSH URL instead |
| "Repository not found" | No HTTPS credentials cached | Use SSH URL or configure gh auth setup-git |
| "Permission denied (publickey)" | SSH key not configured or not added to host | Run ssh -T git@github.com to diagnose |
| Updates always show "skipped" | Unauthenticated API calls return 404 | Reinstall the skill manually |
| Works for some team members, not others | Different credential helper configs | Standardize on SSH URLs across the team |
#!/usr/bin/env node
'use strict';
const https = require('https');
const { execFileSync } = require('child_process');
const ANSI_RE = /\x1b\[[0-9;]*m/g;
const ANSI_RESET = '\x1b[0m';
const ANSI_BOLD = '\x1b[1m';
const ANSI_BLUE = '\x1b[34m';
const ANSI_GRAY = '\x1b[90m';
function stripAnsi(text) {
return text.replace(ANSI_RE, '');
}
function style(text, ansi) {
return `${ansi}${text}${ANSI_RESET}`;
}
function formatName(text) {
return style(text, ANSI_BOLD);
}
function formatUrl(text) {
return style(text, ANSI_BLUE);
}
function formatDescription(text) {
return style(text, ANSI_GRAY);
}
function decodeHtml(text) {
return text
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
.replace(/&/g, '&');
}
function stripTags(html) {
return html.replace(/<[^>]+>/g, ' ');
}
function collapseWhitespace(text) {
return text.replace(/\s+/g, ' ').trim();
}
function runFind(query) {
const parts = query.split(/\s+/).filter(Boolean);
const output = execFileSync('pnpm', ['dlx', 'skills', 'find', ...parts], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
});
return output;
}
function parseResults(output) {
const cleaned = stripAnsi(output);
const lines = cleaned
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean);
const results = [];
for (let i = 0; i < lines.length; i += 1) {
const line = lines[i];
if (
line.includes('@') &&
line.includes('/') &&
!line.includes('Install with')
) {
let url = '';
for (let j = i + 1; j < lines.length; j += 1) {
if (lines[j].includes('skills.sh/')) {
const parts = lines[j].split(/\s+/);
url = parts[parts.length - 1];
break;
}
}
if (url) {
results.push([line, url]);
}
}
}
return results;
}
function fetchUrl(url, timeoutMs, redirects = 0) {
return new Promise((resolve, reject) => {
const req = https.get(
url,
{ headers: { 'User-Agent': 'agent-skills-find' } },
(res) => {
if (
res.statusCode &&
res.statusCode >= 300 &&
res.statusCode < 400 &&
res.headers.location
) {
if (redirects >= 3) {
reject(new Error('Too many redirects'));
res.resume();
return;
}
const nextUrl = new URL(res.headers.location, url).toString();
res.resume();
resolve(fetchUrl(nextUrl, timeoutMs, redirects + 1));
return;
}
if (res.statusCode && res.statusCode >= 400) {
reject(new Error(`HTTP ${res.statusCode}`));
res.resume();
return;
}
let data = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => resolve(data));
},
);
req.on('error', reject);
req.setTimeout(timeoutMs, () => {
req.destroy(new Error('Request timeout'));
});
});
}
function extractMetaDescription(html) {
const patterns = [
/<meta[^>]+name="description"[^>]+content="([^"]+)"/i,
/<meta[^>]+property="og:description"[^>]+content="([^"]+)"/i,
/<meta[^>]+name='description'[^>]+content='([^']+)'/i,
/<meta[^>]+property='og:description'[^>]+content='([^']+)'/i,
];
for (const re of patterns) {
const m = html.match(re);
if (m && m[1]) {
return collapseWhitespace(decodeHtml(m[1]));
}
}
return null;
}
function extractFirstParagraph(html) {
const idx = html.indexOf('prose');
const slice = idx >= 0 ? html.slice(idx) : html;
const m = slice.match(/<p[^>]*>([\s\S]*?)<\/p>/i);
if (!m || !m[1]) return null;
const text = collapseWhitespace(decodeHtml(stripTags(m[1])));
return text || null;
}
async function fetchDescription(url, timeoutMs) {
const body = await fetchUrl(url, timeoutMs);
const first = extractFirstParagraph(body);
if (first) return first;
const meta = extractMetaDescription(body);
if (meta) return meta;
return null;
}
function agentSkillsUrl(skillsUrl) {
const m = skillsUrl.match(
/^https?:\/\/skills\.sh\/([^/]+)\/([^/]+)\/([^/]+)/,
);
if (!m) return null;
const [, owner, repo, skill] = m;
return `https://agent-skills.md/skills/${owner}/${repo}/${skill}`;
}
function parseArgs(argv) {
const args = {
query: null,
max: 10,
timeout: 10,
concurrency: 5,
noFetch: false,
};
const rest = [];
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (token === '--max') {
args.max = Number(argv[++i] || '10');
} else if (token === '--timeout') {
args.timeout = Number(argv[++i] || '10');
} else if (token === '--concurrency') {
args.concurrency = Number(argv[++i] || '5');
} else if (token === '--no-fetch') {
args.noFetch = true;
} else if (token.startsWith('-')) {
throw new Error(`Unknown option: ${token}`);
} else {
rest.push(token);
}
}
args.query = rest.join(' ').trim();
if (!args.query) {
throw new Error('Missing query. Usage: enrich_find.js <query>');
}
return args;
}
async function mapWithConcurrency(items, limit, mapper) {
const results = new Array(items.length);
let index = 0;
async function worker() {
while (true) {
const current = index;
index += 1;
if (current >= items.length) return;
results[current] = await mapper(items[current], current);
}
}
const workers = [];
const count = Math.max(1, Math.min(limit, items.length));
for (let i = 0; i < count; i += 1) {
workers.push(worker());
}
await Promise.all(workers);
return results;
}
async function main() {
let args;
try {
args = parseArgs(process.argv.slice(2));
} catch (err) {
console.error(String(err.message || err));
process.exit(2);
}
let output;
try {
output = runFind(args.query);
} catch (err) {
console.error(String(err.message || err));
process.exit(1);
}
const results = parseResults(output);
if (results.length === 0) {
console.log('No skills found.');
return;
}
const timeoutMs = Math.max(1, args.timeout) * 1000;
const maxCount = Math.max(0, args.max);
const limited = results.slice(0, maxCount);
if (args.noFetch) {
for (const [name, url] of limited) {
console.log(formatName(name));
console.log(`└ ${formatUrl(url)}`);
console.log(formatDescription('[description skipped]'));
console.log('');
}
return;
}
const descriptions = await mapWithConcurrency(
limited,
Math.max(1, args.concurrency || 1),
async ([, url]) => {
let desc = null;
try {
desc = await fetchDescription(url, timeoutMs);
} catch (_) {
desc = null;
}
if (!desc) {
const alt = agentSkillsUrl(url);
if (alt) {
try {
desc = await fetchDescription(alt, timeoutMs);
} catch (_) {
desc = null;
}
}
}
return desc || '[no description found]';
},
);
for (let i = 0; i < limited.length; i += 1) {
const [name, url] = limited[i];
console.log(formatName(name));
console.log(`└ ${formatUrl(url)}`);
console.log(formatDescription(descriptions[i]));
console.log('');
}
}
main().catch((err) => {
console.error(String(err.message || err));
process.exit(1);
});