cyber-skills 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cyber-skills.mts +102 -0
- package/hooks/definitions/commit-discipline.mts +15 -0
- package/hooks/definitions/init.mts +22 -0
- package/hooks/inject-commit-discipline.mts +66 -0
- package/hooks/lib/commit-discipline-content.mts +53 -0
- package/hooks/lib/hook-command.mts +31 -0
- package/hooks/lib/package-root.mts +7 -0
- package/hooks/register-agent-hooks.mts +290 -0
- package/hooks/runtime/commit-discipline.mts +39 -0
- package/package.json +57 -0
- package/readme.md +76 -0
- package/skills/audit-skill/SKILL.md +271 -0
- package/skills/audit-skill/scripts/validate-skills.mts +495 -0
- package/skills/commit/SKILL.md +34 -0
- package/skills/configure-awesome-sources/SKILL.md +73 -0
- package/skills/configure-awesome-sources/scripts/configure-awesome-sources.mts +252 -0
- package/skills/create-skill/SKILL.md +126 -0
- package/skills/find-awesome-skill/SKILL.md +55 -0
- package/skills/find-awesome-skill/scripts/awesome-lib.mts +476 -0
- package/skills/find-awesome-skill/scripts/find-awesome-skill.mts +39 -0
- package/skills/init/SKILL.md +83 -0
- package/skills/init-commit-discipline/SKILL.md +75 -0
- package/skills/init-commit-discipline/scripts/resolve-commit-skill.mts +76 -0
- package/skills/patch-skill/SKILL.md +229 -0
- package/skills/skillify/SKILL.md +110 -0
- package/skills/update-awesome-list/SKILL.md +65 -0
- package/skills/update-awesome-list/scripts/inspect-skills-repo.mts +112 -0
- package/skills/update-awesome-list/scripts/render-awesome-list.mts +91 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'node:fs'
|
|
3
|
+
import * as os from 'node:os'
|
|
4
|
+
import * as path from 'node:path'
|
|
5
|
+
|
|
6
|
+
type SourceClass = 'local-private' | 'repo-shared' | 'global-user'
|
|
7
|
+
|
|
8
|
+
interface SourceRef {
|
|
9
|
+
repo: string
|
|
10
|
+
path: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SourceConfigFile {
|
|
14
|
+
version: 1
|
|
15
|
+
sources?: SourceRef[]
|
|
16
|
+
disabled_sources?: SourceRef[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeRepo(repo: string): string {
|
|
20
|
+
return repo
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/^https?:\/\/github\.com\//, '')
|
|
23
|
+
.replace(/\.git$/, '')
|
|
24
|
+
.replace(/^\/+|\/+$/g, '')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizePath(filePath: string): string {
|
|
28
|
+
return filePath.trim().replace(/^\/+/, '') || 'awesome-skills.json'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sourceKey(source: SourceRef): string {
|
|
32
|
+
return `${normalizeRepo(source.repo)}::${normalizePath(source.path)}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getLayerFilePath(cwd: string, sourceClass: SourceClass): string {
|
|
36
|
+
switch (sourceClass) {
|
|
37
|
+
case 'local-private':
|
|
38
|
+
return path.join(cwd, '.agents', 'awesome-skill-sources.local.json')
|
|
39
|
+
case 'repo-shared':
|
|
40
|
+
return path.join(cwd, '.agents', 'awesome-skill-sources.json')
|
|
41
|
+
case 'global-user':
|
|
42
|
+
return path.join(os.homedir(), '.agents', 'awesome-skill-sources.json')
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseRepositoryFromPackage(cwd: string): string | null {
|
|
47
|
+
const manifestPath = path.join(cwd, 'package.json')
|
|
48
|
+
if (!fs.existsSync(manifestPath)) return null
|
|
49
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as { repository?: { url?: string } | string }
|
|
50
|
+
const repoUrl = typeof manifest.repository === 'string' ? manifest.repository : manifest.repository?.url
|
|
51
|
+
if (!repoUrl) return null
|
|
52
|
+
const match = repoUrl.match(/github\.com[:/](.+?)(?:\.git)?$/)
|
|
53
|
+
return match ? normalizeRepo(match[1]) : null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function loadSourceConfigFile(filePath: string): SourceConfigFile {
|
|
57
|
+
if (!fs.existsSync(filePath)) return { version: 1, sources: [], disabled_sources: [] }
|
|
58
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8')) as SourceConfigFile
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function saveSourceConfigFile(filePath: string, config: SourceConfigFile): void {
|
|
62
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
filePath,
|
|
65
|
+
`${JSON.stringify(
|
|
66
|
+
{
|
|
67
|
+
version: 1,
|
|
68
|
+
...(config.sources && config.sources.length > 0 ? { sources: config.sources } : {}),
|
|
69
|
+
...(config.disabled_sources && config.disabled_sources.length > 0
|
|
70
|
+
? { disabled_sources: config.disabled_sources }
|
|
71
|
+
: {}),
|
|
72
|
+
},
|
|
73
|
+
null,
|
|
74
|
+
2,
|
|
75
|
+
)}\n`,
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseArgs(argv: string[]) {
|
|
80
|
+
const args = argv.slice(2)
|
|
81
|
+
const command = args[0]
|
|
82
|
+
const layerValue = readFlag(args, '--layer')
|
|
83
|
+
const repo = readFlag(args, '--repo')
|
|
84
|
+
const filePath = normalizePath(readFlag(args, '--path') ?? 'awesome-skills.json')
|
|
85
|
+
return { command, layerValue, repo, filePath }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readFlag(args: string[], flag: string): string | undefined {
|
|
89
|
+
const index = args.indexOf(flag)
|
|
90
|
+
return index === -1 ? undefined : args[index + 1]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveLayer(value: string | undefined): SourceClass {
|
|
94
|
+
switch (value) {
|
|
95
|
+
case 'local':
|
|
96
|
+
case 'local-private':
|
|
97
|
+
return 'local-private'
|
|
98
|
+
case 'repo':
|
|
99
|
+
case 'repo-shared':
|
|
100
|
+
return 'repo-shared'
|
|
101
|
+
case 'global':
|
|
102
|
+
case 'global-user':
|
|
103
|
+
return 'global-user'
|
|
104
|
+
default:
|
|
105
|
+
throw new Error('Expected --layer local|repo|global')
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function mutateRefs(refs: SourceRef[], predicate: (ref: SourceRef) => boolean, next?: SourceRef): SourceRef[] {
|
|
110
|
+
const kept = refs.filter((ref) => !predicate(ref))
|
|
111
|
+
if (next) kept.push(next)
|
|
112
|
+
return kept.sort((a, b) => sourceKey(a).localeCompare(sourceKey(b)))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getCurrentSourceSet(config: SourceConfigFile): Set<string> {
|
|
116
|
+
return new Set([...(config.sources ?? []), ...(config.disabled_sources ?? [])].map(sourceKey))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getLowerLayerSources(cwd: string, layer: SourceClass): Set<string> {
|
|
120
|
+
const all: SourceClass[] = ['global-user', 'repo-shared', 'local-private']
|
|
121
|
+
const index = all.indexOf(layer)
|
|
122
|
+
const refs = new Set<string>()
|
|
123
|
+
for (const item of all.slice(0, index)) {
|
|
124
|
+
const config = loadSourceConfigFile(getLayerFilePath(cwd, item))
|
|
125
|
+
for (const ref of config.sources ?? []) refs.add(sourceKey(ref))
|
|
126
|
+
}
|
|
127
|
+
return refs
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getResolvedSources(cwd: string): Array<SourceRef & { sourceClass: SourceClass | 'default' }> {
|
|
131
|
+
const layers: Array<{ sourceClass: SourceClass; path: string }> = [
|
|
132
|
+
{ sourceClass: 'local-private', path: getLayerFilePath(cwd, 'local-private') },
|
|
133
|
+
{ sourceClass: 'repo-shared', path: getLayerFilePath(cwd, 'repo-shared') },
|
|
134
|
+
{ sourceClass: 'global-user', path: getLayerFilePath(cwd, 'global-user') },
|
|
135
|
+
]
|
|
136
|
+
const disabled = new Set<string>()
|
|
137
|
+
const kept = new Map<string, SourceRef & { sourceClass: SourceClass | 'default' }>()
|
|
138
|
+
|
|
139
|
+
for (const layer of layers) {
|
|
140
|
+
const config = loadSourceConfigFile(layer.path)
|
|
141
|
+
for (const ref of config.disabled_sources ?? []) disabled.add(sourceKey(ref))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const currentRepo = parseRepositoryFromPackage(cwd)
|
|
145
|
+
if (currentRepo) {
|
|
146
|
+
const ref = { repo: currentRepo, path: 'awesome-skills.json' }
|
|
147
|
+
const key = sourceKey(ref)
|
|
148
|
+
if (!disabled.has(key) && fs.existsSync(path.join(cwd, ref.path))) {
|
|
149
|
+
kept.set(key, { ...ref, sourceClass: 'default' })
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const layer of layers.reverse()) {
|
|
154
|
+
const config = loadSourceConfigFile(layer.path)
|
|
155
|
+
for (const ref of config.sources ?? []) {
|
|
156
|
+
const key = sourceKey(ref)
|
|
157
|
+
if (disabled.has(key) || kept.has(key)) continue
|
|
158
|
+
kept.set(key, { ...ref, sourceClass: layer.sourceClass })
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Array.from(kept.values())
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function printUsage(): void {
|
|
166
|
+
console.log('Usage:')
|
|
167
|
+
console.log(' tsx skills/configure-awesome-sources/scripts/configure-awesome-sources.mts list')
|
|
168
|
+
console.log(
|
|
169
|
+
' tsx skills/configure-awesome-sources/scripts/configure-awesome-sources.mts add --layer local|repo|global --repo owner/name [--path awesome-skills.json]',
|
|
170
|
+
)
|
|
171
|
+
console.log(
|
|
172
|
+
' tsx skills/configure-awesome-sources/scripts/configure-awesome-sources.mts remove --layer local|repo|global --repo owner/name [--path awesome-skills.json]',
|
|
173
|
+
)
|
|
174
|
+
console.log(
|
|
175
|
+
' tsx skills/configure-awesome-sources/scripts/configure-awesome-sources.mts disable --layer local|repo|global --repo owner/name [--path awesome-skills.json]',
|
|
176
|
+
)
|
|
177
|
+
console.log(
|
|
178
|
+
' tsx skills/configure-awesome-sources/scripts/configure-awesome-sources.mts enable --layer local|repo|global --repo owner/name [--path awesome-skills.json]',
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const cwd = process.cwd()
|
|
183
|
+
const { command, layerValue, repo, filePath } = parseArgs(process.argv)
|
|
184
|
+
|
|
185
|
+
if (!command || command === '--help' || command === 'help') {
|
|
186
|
+
printUsage()
|
|
187
|
+
process.exit(0)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (command === 'list') {
|
|
191
|
+
const sources = getResolvedSources(cwd)
|
|
192
|
+
if (sources.length === 0) {
|
|
193
|
+
console.log('No awesome sources configured.')
|
|
194
|
+
process.exit(0)
|
|
195
|
+
}
|
|
196
|
+
console.log('Effective awesome sources:')
|
|
197
|
+
for (const source of sources) console.log(`- ${source.repo} (${source.path}) [${source.sourceClass}]`)
|
|
198
|
+
process.exit(0)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const layer = resolveLayer(layerValue)
|
|
202
|
+
if (!repo) throw new Error('Expected --repo owner/name')
|
|
203
|
+
const ref = { repo: normalizeRepo(repo), path: filePath }
|
|
204
|
+
const configPath = getLayerFilePath(cwd, layer)
|
|
205
|
+
const config = loadSourceConfigFile(configPath)
|
|
206
|
+
const predicate = (item: SourceRef) => sourceKey(item) === sourceKey(ref)
|
|
207
|
+
const currentRefs = getCurrentSourceSet(config)
|
|
208
|
+
const inheritedSources = getLowerLayerSources(cwd, layer)
|
|
209
|
+
|
|
210
|
+
switch (command) {
|
|
211
|
+
case 'add':
|
|
212
|
+
config.sources = mutateRefs(config.sources ?? [], predicate, ref)
|
|
213
|
+
config.disabled_sources = mutateRefs(config.disabled_sources ?? [], predicate)
|
|
214
|
+
saveSourceConfigFile(configPath, config)
|
|
215
|
+
console.log(`Added ${ref.repo} (${ref.path}) to ${configPath}`)
|
|
216
|
+
break
|
|
217
|
+
case 'remove':
|
|
218
|
+
config.sources = mutateRefs(config.sources ?? [], predicate)
|
|
219
|
+
config.disabled_sources = mutateRefs(config.disabled_sources ?? [], predicate)
|
|
220
|
+
saveSourceConfigFile(configPath, config)
|
|
221
|
+
console.log(`Removed ${ref.repo} (${ref.path}) from ${configPath}`)
|
|
222
|
+
break
|
|
223
|
+
case 'disable':
|
|
224
|
+
config.sources = mutateRefs(config.sources ?? [], predicate)
|
|
225
|
+
config.disabled_sources = mutateRefs(config.disabled_sources ?? [], predicate, ref)
|
|
226
|
+
saveSourceConfigFile(configPath, config)
|
|
227
|
+
console.log(`Disabled ${ref.repo} (${ref.path}) in ${configPath}`)
|
|
228
|
+
break
|
|
229
|
+
case 'enable':
|
|
230
|
+
config.disabled_sources = mutateRefs(config.disabled_sources ?? [], predicate)
|
|
231
|
+
if (!currentRefs.has(sourceKey(ref)) && !inheritedSources.has(sourceKey(ref))) {
|
|
232
|
+
config.sources = mutateRefs(config.sources ?? [], predicate, ref)
|
|
233
|
+
console.log(`Enabled ${ref.repo} (${ref.path}) in ${configPath} and added it as a direct source.`)
|
|
234
|
+
} else {
|
|
235
|
+
config.sources = mutateRefs(config.sources ?? [], predicate)
|
|
236
|
+
console.log(`Enabled ${ref.repo} (${ref.path}) in ${configPath}.`)
|
|
237
|
+
}
|
|
238
|
+
saveSourceConfigFile(configPath, config)
|
|
239
|
+
break
|
|
240
|
+
default:
|
|
241
|
+
printUsage()
|
|
242
|
+
process.exit(1)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const currentRepo = parseRepositoryFromPackage(cwd)
|
|
246
|
+
if (
|
|
247
|
+
currentRepo &&
|
|
248
|
+
sourceKey(ref) === sourceKey({ repo: currentRepo, path: 'awesome-skills.json' }) &&
|
|
249
|
+
command === 'disable'
|
|
250
|
+
) {
|
|
251
|
+
console.log('Note: this disables the built-in default source for the current repo as well.')
|
|
252
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-skill
|
|
3
|
+
description: Use this skill when the user asks to create a new agent skill. Creates the skill directory under ~/.agents/skills/ and links it into all detected agents so they can pick it up.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create Skill
|
|
7
|
+
|
|
8
|
+
When the user asks to create a new skill, follow this convention.
|
|
9
|
+
|
|
10
|
+
## Directory structure
|
|
11
|
+
|
|
12
|
+
Skills live in `~/.agents/skills/<name>/` and are linked into each agent's skills directory:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
~/.agents/skills/
|
|
16
|
+
<name>/
|
|
17
|
+
SKILL.md ← source of truth, edit this
|
|
18
|
+
~/.claude/skills/
|
|
19
|
+
<name> ← symlink → ~/.agents/skills/<name> (Claude Code)
|
|
20
|
+
# ...and equivalent paths for other detected agents
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
### 1. Create the skill
|
|
26
|
+
|
|
27
|
+
Check whether `npx skills` is available:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx skills --version 2>/dev/null
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**If available**, use it to scaffold the skill:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx skills init <name> --dir ~/.agents/skills
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This creates `~/.agents/skills/<name>/SKILL.md` with a starter template. Edit that file to fill in the real content.
|
|
40
|
+
|
|
41
|
+
**If not available**, create manually:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
mkdir -p ~/.agents/skills/<name>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then write `~/.agents/skills/<name>/SKILL.md` using this template:
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
---
|
|
51
|
+
name: <name>
|
|
52
|
+
description: Use this skill when <trigger condition>. <One-line summary of what it does.>
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# <Name>
|
|
56
|
+
|
|
57
|
+
## When to use
|
|
58
|
+
|
|
59
|
+
<Describe when this skill should be used.>
|
|
60
|
+
|
|
61
|
+
## Instructions
|
|
62
|
+
|
|
63
|
+
1. First step
|
|
64
|
+
2. Second step
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Audit the skill
|
|
68
|
+
|
|
69
|
+
Run automated checks first:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx tsx .agents/skills/audit-skill/scripts/validate-skills.mts --path ~/.agents/skills/<name>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Fix any CRITICAL findings before proceeding.
|
|
76
|
+
|
|
77
|
+
Then invoke the **audit-skill** agent skill for full review (Q6–Q12, E3–E8, P1–P3). Do not continue to step 3 if any CRITICAL findings remain.
|
|
78
|
+
|
|
79
|
+
### 3. Link to agents
|
|
80
|
+
|
|
81
|
+
**If `npx skills` is available:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx skills add ~/.agents/skills/<name>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This detects all installed agents and prompts the user to choose which ones to link. It handles the correct path for each agent (Claude Code, Cursor, Codex, OpenCode, etc.).
|
|
88
|
+
|
|
89
|
+
**Known issue:** `npx skills` has a bug where it may not create `~/.claude/skills/` if the directory doesn't exist yet. After linking, verify:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ls ~/.claude/skills/<name>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If missing, fall back to the manual step below.
|
|
96
|
+
|
|
97
|
+
**If `npx skills` is not available, or the symlink is missing after the above:**
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
ln -sf ~/.agents/skills/<name> ~/.claude/skills/<name>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Adjust the target path for other agents as needed (e.g., `~/.cursor/skills/`, `~/.opencode/skills/`).
|
|
104
|
+
|
|
105
|
+
## What makes a good skill
|
|
106
|
+
|
|
107
|
+
- **Decisions over documentation.** Encode what to decide and how — don't repeat reference material the model already knows.
|
|
108
|
+
- **Narrow and composable.** One workflow per skill. Skills can be triggered by situation (user-facing) or called by other skills (sub-skills). Sub-skills have no situational trigger — their `description` should say "Internal skill: called by X" to avoid accidental activation. Neither type should be loaded as ambient context.
|
|
109
|
+
- **No baked-in opinions.** Detect the user's setup (package manager, monorepo shape, tooling) at runtime rather than assuming a specific stack.
|
|
110
|
+
|
|
111
|
+
## Skill script output discipline
|
|
112
|
+
|
|
113
|
+
If the skill includes a `scripts/` directory, apply these rules so agents do not confuse human output with machine results:
|
|
114
|
+
|
|
115
|
+
- **Stdout is the machine contract** — one JSON value or silence; no prose, tables, or progress in default mode
|
|
116
|
+
- **Files hold durable state** — agents read artifacts by path named in stdout JSON or SKILL.md
|
|
117
|
+
- **Stderr is human/diagnostic** — use `console.warn` / `console.error`; gate tables and progress behind `--verbose`
|
|
118
|
+
- **Skills present; scripts record** — SKILL.md tells the agent what file or field to read; the agent summarizes for the user
|
|
119
|
+
- **Autonomous runs** — document `--yes` or equivalent non-interactive flags; never put prompts on stdout
|
|
120
|
+
- **Implementation** — use `process.stdout.write(JSON.stringify(result) + '\n')` for contract output; never `console.log`
|
|
121
|
+
|
|
122
|
+
## Notes
|
|
123
|
+
|
|
124
|
+
- `~/.agents/skills/` is the source of truth — commit or back up this directory.
|
|
125
|
+
- Agent skills directories (e.g. `~/.claude/skills/`) only contain symlinks; never edit files there directly.
|
|
126
|
+
- The `description` frontmatter field is what agents read to decide when to activate the skill — make it specific and include "Use this skill when" trigger language. For sub-skills, prefix with "Internal skill:" to prevent unintended activation.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: find-awesome-skill
|
|
3
|
+
description: Use this skill when looking for curated skill recommendations from awesome lists, with exact install commands.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Find Awesome Skill
|
|
7
|
+
|
|
8
|
+
Search the user's configured awesome skill sources, merge duplicates, and return concise recommendations with install commands.
|
|
9
|
+
|
|
10
|
+
## Source discovery
|
|
11
|
+
|
|
12
|
+
Awesome sources are loaded from these layers, in this order:
|
|
13
|
+
|
|
14
|
+
1. `.agents/awesome-skill-sources.local.json`
|
|
15
|
+
2. `.agents/awesome-skill-sources.json`
|
|
16
|
+
3. `~/.agents/awesome-skill-sources.json`
|
|
17
|
+
4. The current repo's own `awesome-skills.json` as the default source unless explicitly disabled
|
|
18
|
+
|
|
19
|
+
Duplicate sources are deduped by `repo + path`.
|
|
20
|
+
|
|
21
|
+
## Search flow
|
|
22
|
+
|
|
23
|
+
1. Run the bundled finder:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx tsx skills/find-awesome-skill/scripts/find-awesome-skill.mts "<query>"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
For machine-readable output:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx tsx skills/find-awesome-skill/scripts/find-awesome-skill.mts "<query>" --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. Load configured awesome sources from the three config layers plus the default source.
|
|
36
|
+
3. Merge duplicate repo or skill entries across sources.
|
|
37
|
+
4. Rank matches by:
|
|
38
|
+
- exact name match
|
|
39
|
+
- summary match
|
|
40
|
+
- tag match
|
|
41
|
+
- corroboration count
|
|
42
|
+
- source class preference
|
|
43
|
+
5. Return concise results with:
|
|
44
|
+
- repo or repo+skill identity
|
|
45
|
+
- why it matched
|
|
46
|
+
- primary `why_recommended`
|
|
47
|
+
- whether other sources also recommended it
|
|
48
|
+
- exact `npx skills add ...` install command
|
|
49
|
+
|
|
50
|
+
## Guidance
|
|
51
|
+
|
|
52
|
+
- Exact name matches should be treated as the strongest signal.
|
|
53
|
+
- Tag and summary matches matter next.
|
|
54
|
+
- Corroboration across multiple awesome sources should increase confidence but should not hide why a result matched.
|
|
55
|
+
- If no results match, say so directly and suggest adding or configuring more awesome sources.
|