learn-skill.md 1.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/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # learn-skill
2
+
3
+ Install AI agent skills from [learn-skill.md](https://learn-skill.md) — the skill marketplace for AI agents.
4
+
5
+ ## Quick Install
6
+
7
+ ```bash
8
+ npx learn-skill add <slug>
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # install a skill (interactive agent picker)
15
+ npx learn-skill add my-skill
16
+
17
+ # install to a specific agent
18
+ npx learn-skill add my-skill -a claude
19
+ npx learn-skill add my-skill -a cursor
20
+
21
+ # skip confirmation
22
+ npx learn-skill add my-skill -a claude -y
23
+
24
+ # search for skills
25
+ npx learn-skill search "unit test"
26
+
27
+ # list recent skills
28
+ npx learn-skill list
29
+
30
+ # show supported agents
31
+ npx learn-skill agents
32
+ ```
33
+
34
+ ## Supported Agents
35
+
36
+ | Agent | Skills Directory |
37
+ |-------|-----------------|
38
+ | Claude Code | `~/.claude/skills/` |
39
+ | Cursor | `~/.cursor/skills/` |
40
+ | Codex | `~/.codex/skills/` |
41
+ | Windsurf | `~/.windsurf/skills/` |
42
+ | Continue | `~/.continue/skills/` |
43
+ | OpenCode | `~/.opencode/skills/` |
44
+ | Amp | `~/.amp/skills/` |
45
+
46
+ The CLI auto-detects which agents are installed on your machine.
47
+
48
+ ## How It Works
49
+
50
+ 1. Fetches the skill from learn-skill.md
51
+ 2. Asks which agent(s) to install to (or use `-a` flag)
52
+ 3. Downloads the skill as `.tar.gz`
53
+ 4. Extracts to the agent's skills directory
54
+
55
+ ## Also works with
56
+
57
+ ```bash
58
+ bunx learn-skill add my-skill
59
+ pnpm dlx learn-skill add my-skill
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
package/bin/cli.mjs ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from 'node:util'
4
+ import { add } from '../src/install.mjs'
5
+ import { search, list } from '../src/search.mjs'
6
+ import { AGENTS, detectAgents } from '../src/agents.mjs'
7
+
8
+ const HELP = `
9
+ learn-skill.md — install AI agent skills from learn-skill.md
10
+
11
+ usage:
12
+ learn-skill.md add <slug> install a skill
13
+ learn-skill.md add <slug> -a claude install to a specific agent
14
+ learn-skill.md search <query> search for skills
15
+ learn-skill.md list list recent skills
16
+ learn-skill.md agents show supported agents
17
+
18
+ options:
19
+ -a, --agent <name> target agent (claude, cursor, codex, windsurf, etc.)
20
+ -y, --yes skip confirmation
21
+ -h, --help show this help
22
+ -v, --version show version
23
+
24
+ examples:
25
+ npx learn-skill.md add my-skill
26
+ npx learn-skill.md add my-skill -a cursor
27
+ npx learn-skill.md search "unit test"
28
+ `
29
+
30
+ const { values, positionals } = parseArgs({
31
+ allowPositionals: true,
32
+ options: {
33
+ agent: { type: 'string', short: 'a' },
34
+ yes: { type: 'boolean', short: 'y', default: false },
35
+ help: { type: 'boolean', short: 'h', default: false },
36
+ version: { type: 'boolean', short: 'v', default: false },
37
+ },
38
+ })
39
+
40
+ const [command, ...args] = positionals
41
+
42
+ if (values.version) {
43
+ console.log('learn-skill.md v1.0.0')
44
+ process.exit(0)
45
+ }
46
+
47
+ if (values.help || !command) {
48
+ console.log(HELP)
49
+ process.exit(0)
50
+ }
51
+
52
+ async function main() {
53
+ switch (command) {
54
+ case 'add':
55
+ case 'install': {
56
+ const slug = args[0]
57
+ if (!slug) {
58
+ console.error(' error: missing skill slug\n usage: learn-skill.md add <slug>')
59
+ process.exit(1)
60
+ }
61
+ await add(slug, { agent: values.agent, yes: values.yes })
62
+ break
63
+ }
64
+ case 'search':
65
+ case 'find': {
66
+ const query = args.join(' ')
67
+ if (!query) {
68
+ console.error(' error: missing search query\n usage: learn-skill.md search <query>')
69
+ process.exit(1)
70
+ }
71
+ await search(query)
72
+ break
73
+ }
74
+ case 'list':
75
+ case 'ls': {
76
+ await list()
77
+ break
78
+ }
79
+ case 'agents': {
80
+ console.log('\n supported agents:\n')
81
+ const detected = detectAgents()
82
+ for (const a of AGENTS) {
83
+ const installed = detected.includes(a.id) ? ' (detected)' : ''
84
+ console.log(` ${a.id.padEnd(14)} ${a.dir}${installed}`)
85
+ }
86
+ console.log()
87
+ break
88
+ }
89
+ default:
90
+ console.error(` unknown command: ${command}`)
91
+ console.log(HELP)
92
+ process.exit(1)
93
+ }
94
+ }
95
+
96
+ main().catch((err) => {
97
+ console.error(`\n error: ${err.message}`)
98
+ process.exit(1)
99
+ })
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "learn-skill.md",
3
+ "version": "1.0.0",
4
+ "description": "Install AI agent skills from learn-skill.md — the skill marketplace for AI agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "learn-skill.md": "./bin/cli.mjs"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "ai",
19
+ "skills",
20
+ "claude-code",
21
+ "cursor",
22
+ "codex",
23
+ "windsurf",
24
+ "continue",
25
+ "opencode",
26
+ "amp",
27
+ "agent",
28
+ "skill-installer",
29
+ "skill-marketplace",
30
+ "learn-skill",
31
+ "npx"
32
+ ],
33
+ "author": "froogooofficial",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/froogooofficial/lear-skill.com"
38
+ },
39
+ "homepage": "https://learn-skill.md"
40
+ }
package/src/agents.mjs ADDED
@@ -0,0 +1,34 @@
1
+ import { homedir } from 'node:os'
2
+ import { join } from 'node:path'
3
+ import { existsSync } from 'node:fs'
4
+
5
+ const home = homedir()
6
+
7
+ export const AGENTS = [
8
+ { id: 'claude', name: 'Claude Code', dir: '.claude/skills', configDir: '.claude' },
9
+ { id: 'cursor', name: 'Cursor', dir: '.cursor/skills', configDir: '.cursor' },
10
+ { id: 'codex', name: 'Codex', dir: '.codex/skills', configDir: '.codex' },
11
+ { id: 'windsurf', name: 'Windsurf', dir: '.windsurf/skills', configDir: '.windsurf' },
12
+ { id: 'continue', name: 'Continue', dir: '.continue/skills', configDir: '.continue' },
13
+ { id: 'opencode', name: 'OpenCode', dir: '.opencode/skills', configDir: '.opencode' },
14
+ { id: 'amp', name: 'Amp', dir: '.amp/skills', configDir: '.amp' },
15
+ ]
16
+
17
+ export function detectAgents() {
18
+ const found = []
19
+ for (const agent of AGENTS) {
20
+ const configPath = join(home, agent.configDir)
21
+ if (existsSync(configPath)) {
22
+ found.push(agent.id)
23
+ }
24
+ }
25
+ return found
26
+ }
27
+
28
+ export function getAgent(id) {
29
+ return AGENTS.find((a) => a.id === id)
30
+ }
31
+
32
+ export function getSkillDir(agent) {
33
+ return join(home, agent.dir)
34
+ }
package/src/api.mjs ADDED
@@ -0,0 +1,27 @@
1
+ const BASE_URL = 'https://learn-skill.md'
2
+
3
+ export async function get(path) {
4
+ const url = `${BASE_URL}${path}`
5
+ const res = await fetch(url)
6
+ if (!res.ok) {
7
+ throw new Error(`API error: ${res.status} ${res.statusText} (${path})`)
8
+ }
9
+ return res.json()
10
+ }
11
+
12
+ export async function download(path) {
13
+ const url = `${BASE_URL}${path}`
14
+ const res = await fetch(url)
15
+ if (!res.ok) {
16
+ throw new Error(`Download failed: ${res.status} ${res.statusText} (${path})`)
17
+ }
18
+ return res
19
+ }
20
+
21
+ export function rawUrl(slug) {
22
+ return `${BASE_URL}/raw/${slug}`
23
+ }
24
+
25
+ export function installUrl(slug) {
26
+ return `${BASE_URL}/install/${slug}`
27
+ }
@@ -0,0 +1,99 @@
1
+ import { mkdirSync, writeFileSync, existsSync, createWriteStream } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { execSync } from 'node:child_process'
4
+ import { tmpdir } from 'node:os'
5
+ import { pipeline } from 'node:stream/promises'
6
+ import { Readable } from 'node:stream'
7
+
8
+ import { AGENTS, detectAgents, getAgent, getSkillDir } from './agents.mjs'
9
+ import { get, installUrl } from './api.mjs'
10
+ import { choose, chooseMultiple, confirm } from './prompt.mjs'
11
+
12
+ export async function add(slug, opts = {}) {
13
+ // 1. fetch skill metadata
14
+ console.log(`\n fetching skill: ${slug}...`)
15
+ const data = await get(`/api/skills/${slug}`)
16
+ const skill = data.skill
17
+
18
+ if (!skill) {
19
+ throw new Error(`skill "${slug}" not found`)
20
+ }
21
+
22
+ console.log(` found: ${skill.title}`)
23
+ if (skill.description) console.log(` ${skill.description}`)
24
+ console.log(` files: ${skill.file_count} | size: ${(skill.file_size / 1024).toFixed(1)}KB`)
25
+
26
+ // 2. pick agent(s)
27
+ let agents = []
28
+
29
+ if (opts.agent) {
30
+ const agent = getAgent(opts.agent)
31
+ if (!agent) {
32
+ throw new Error(
33
+ `unknown agent "${opts.agent}". supported: ${AGENTS.map((a) => a.id).join(', ')}`
34
+ )
35
+ }
36
+ agents = [agent]
37
+ } else {
38
+ const detected = detectAgents()
39
+ const agentOptions = AGENTS.map((a) => ({
40
+ label: `${a.name}${detected.includes(a.id) ? ' (detected)' : ''}`,
41
+ value: a,
42
+ }))
43
+
44
+ const selected = await chooseMultiple('install to which agent(s)?', agentOptions)
45
+ agents = selected.map((s) => s.value)
46
+ }
47
+
48
+ if (agents.length === 0) {
49
+ console.log(' no agents selected, aborting.')
50
+ return
51
+ }
52
+
53
+ // 3. confirm
54
+ if (!opts.yes) {
55
+ console.log('\n will install to:')
56
+ for (const a of agents) {
57
+ console.log(` - ${a.name} (${getSkillDir(a)})`)
58
+ }
59
+ const ok = await confirm('proceed?')
60
+ if (!ok) {
61
+ console.log(' cancelled.')
62
+ return
63
+ }
64
+ }
65
+
66
+ // 4. download tar.gz to temp
67
+ console.log('\n downloading...')
68
+ const url = installUrl(slug)
69
+ const res = await fetch(url)
70
+ if (!res.ok) {
71
+ throw new Error(`download failed: ${res.status}`)
72
+ }
73
+
74
+ const tmpFile = join(tmpdir(), `learn-skill-${slug}-${Date.now()}.tar.gz`)
75
+ const fileStream = createWriteStream(tmpFile)
76
+ await pipeline(Readable.fromWeb(res.body), fileStream)
77
+
78
+ // 5. extract to each agent's skill dir
79
+ for (const agent of agents) {
80
+ const skillDir = getSkillDir(agent)
81
+ mkdirSync(skillDir, { recursive: true })
82
+
83
+ console.log(` installing to ${agent.name}...`)
84
+ try {
85
+ execSync(`tar -xzf "${tmpFile}" -C "${skillDir}"`, { stdio: 'pipe' })
86
+ console.log(` done: ${skillDir}/${slug}/`)
87
+ } catch (err) {
88
+ console.error(` failed to extract to ${agent.name}: ${err.message}`)
89
+ }
90
+ }
91
+
92
+ // 6. cleanup
93
+ try {
94
+ const { unlinkSync } = await import('node:fs')
95
+ unlinkSync(tmpFile)
96
+ } catch {}
97
+
98
+ console.log(`\n skill "${skill.title}" installed successfully!\n`)
99
+ }
package/src/prompt.mjs ADDED
@@ -0,0 +1,54 @@
1
+ import { createInterface } from 'node:readline'
2
+
3
+ const rl = () =>
4
+ createInterface({ input: process.stdin, output: process.stdout })
5
+
6
+ export function ask(question) {
7
+ return new Promise((resolve) => {
8
+ const r = rl()
9
+ r.question(question, (answer) => {
10
+ r.close()
11
+ resolve(answer.trim())
12
+ })
13
+ })
14
+ }
15
+
16
+ export async function choose(label, options) {
17
+ console.log(`\n ${label}\n`)
18
+ options.forEach((opt, i) => {
19
+ console.log(` ${i + 1}) ${opt.label}`)
20
+ })
21
+ console.log()
22
+
23
+ while (true) {
24
+ const answer = await ask(` select [1-${options.length}]: `)
25
+ const num = parseInt(answer, 10)
26
+ if (num >= 1 && num <= options.length) {
27
+ return options[num - 1]
28
+ }
29
+ console.log(` invalid choice, enter 1-${options.length}`)
30
+ }
31
+ }
32
+
33
+ export async function chooseMultiple(label, options) {
34
+ console.log(`\n ${label}\n`)
35
+ options.forEach((opt, i) => {
36
+ console.log(` ${i + 1}) ${opt.label}`)
37
+ })
38
+ console.log()
39
+
40
+ while (true) {
41
+ const answer = await ask(` select [1-${options.length}, comma-separated]: `)
42
+ const nums = answer.split(',').map((s) => parseInt(s.trim(), 10))
43
+ const valid = nums.every((n) => n >= 1 && n <= options.length)
44
+ if (valid && nums.length > 0) {
45
+ return nums.map((n) => options[n - 1])
46
+ }
47
+ console.log(` invalid choice, enter numbers 1-${options.length} separated by commas`)
48
+ }
49
+ }
50
+
51
+ export async function confirm(message) {
52
+ const answer = await ask(` ${message} [y/N]: `)
53
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'
54
+ }
package/src/search.mjs ADDED
@@ -0,0 +1,35 @@
1
+ import { get } from './api.mjs'
2
+
3
+ export async function search(query) {
4
+ const data = await get(`/api/skills?q=${encodeURIComponent(query)}&limit=20`)
5
+ const skills = data.skills || []
6
+
7
+ if (skills.length === 0) {
8
+ console.log(`\n no skills found for "${query}"\n`)
9
+ return
10
+ }
11
+
12
+ console.log(`\n ${skills.length} skill(s) found:\n`)
13
+ for (const s of skills) {
14
+ const tags = s.tags?.length ? ` [${s.tags.join(', ')}]` : ''
15
+ console.log(` ${s.slug.padEnd(30)} ${s.description || ''}${tags}`)
16
+ }
17
+ console.log(`\n install: npx learn-skill add <slug>\n`)
18
+ }
19
+
20
+ export async function list() {
21
+ const data = await get('/api/skills/recent')
22
+ const skills = data.skills || []
23
+
24
+ if (skills.length === 0) {
25
+ console.log('\n no skills available yet\n')
26
+ return
27
+ }
28
+
29
+ console.log('\n recent skills:\n')
30
+ for (const s of skills) {
31
+ const tags = s.tags?.length ? ` [${s.tags.join(', ')}]` : ''
32
+ console.log(` ${s.slug.padEnd(30)} ${s.description || ''}${tags}`)
33
+ }
34
+ console.log(`\n install: npx learn-skill add <slug>\n`)
35
+ }