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.
@@ -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.