alpaki-skills 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/LICENSE +21 -0
- package/README.md +66 -0
- package/bin/alpaki-skills.js +9 -0
- package/lib/api.js +108 -0
- package/lib/cli.js +170 -0
- package/lib/paths.js +23 -0
- package/lib/print.js +78 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alpaki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# alpaki-skills
|
|
2
|
+
|
|
3
|
+
CLI oficial de [Alpaki](https://alpaki.app) para instalar skills en **Cursor** o **Claude Code**.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx alpaki-skills@latest --skill creative-design/3d-web-experience
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Qué hace
|
|
10
|
+
|
|
11
|
+
1. Descarga la skill desde la API de Alpaki (`api.alpaki.app`).
|
|
12
|
+
2. La guarda como `SKILL.md` en:
|
|
13
|
+
- Cursor: `./.cursor/skills/<slug>/SKILL.md`
|
|
14
|
+
- Claude: `./.claude/skills/<slug>/SKILL.md` (`--tool claude`)
|
|
15
|
+
|
|
16
|
+
No empaqueta las ~800 skills en el npm: se resuelven on-demand, igual que el catálogo web.
|
|
17
|
+
|
|
18
|
+
## Uso
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Instalar una skill (mismo comando que muestra alpaki.app/skills)
|
|
22
|
+
npx alpaki-skills@latest --skill creative-design/3d-web-experience
|
|
23
|
+
|
|
24
|
+
# Atajo sin flag
|
|
25
|
+
npx alpaki-skills@latest development/api-design
|
|
26
|
+
|
|
27
|
+
# Listar / buscar
|
|
28
|
+
npx alpaki-skills@latest list
|
|
29
|
+
npx alpaki-skills@latest search "frontend"
|
|
30
|
+
npx alpaki-skills@latest list --category development
|
|
31
|
+
|
|
32
|
+
# Claude Code
|
|
33
|
+
npx alpaki-skills@latest --skill git/commit --tool claude
|
|
34
|
+
|
|
35
|
+
# Destino custom o global
|
|
36
|
+
npx alpaki-skills@latest --skill utilities/summarize --dir ./skills
|
|
37
|
+
npx alpaki-skills@latest --skill utilities/summarize --global
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API local / staging
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ALPAKI_API_BASE=http://localhost:8881 npx alpaki-skills --skill creative-design/3d-web-experience
|
|
44
|
+
# o
|
|
45
|
+
npx alpaki-skills --skill creative-design/3d-web-experience --api http://localhost:8881
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Publicar en npm
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm login
|
|
52
|
+
npm publish --access public
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Después de publicar, `npx alpaki-skills@latest` funcionará para cualquiera.
|
|
56
|
+
|
|
57
|
+
## Desarrollo
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm start -- --help
|
|
61
|
+
npm start -- --skill creative-design/3d-web-experience --api http://localhost:8881
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Licencia
|
|
65
|
+
|
|
66
|
+
MIT. El contenido de cada skill conserva la atribución del origen (claude-code-templates / davila7) cuando aplica.
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const DEFAULT_API_BASE = 'https://api.alpaki.app'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} raw
|
|
5
|
+
*/
|
|
6
|
+
export function resolveSkillPath(raw) {
|
|
7
|
+
const value = String(raw || '').trim().replace(/^\/+|\/+$/g, '')
|
|
8
|
+
if (!value) {
|
|
9
|
+
throw new Error('Debes indicar --skill categoria/slug (ej. creative-design/3d-web-experience).')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const parts = value.split('/').filter(Boolean)
|
|
13
|
+
if (parts.length === 1) {
|
|
14
|
+
return { category: '', slug: parts[0], path: parts[0] }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (parts.length !== 2) {
|
|
18
|
+
throw new Error(`Ruta de skill inválida: "${raw}". Usa categoria/slug.`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
category: parts[0],
|
|
23
|
+
slug: parts[1],
|
|
24
|
+
path: `${parts[0]}/${parts[1]}`,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {{ category: string, slug: string, path: string }} skillPath
|
|
30
|
+
* @param {{ apiBase?: string }} [options]
|
|
31
|
+
*/
|
|
32
|
+
export async function fetchSkill(skillPath, options = {}) {
|
|
33
|
+
const apiBase = normalizeApiBase(options.apiBase)
|
|
34
|
+
const bySlugUrl = `${apiBase}/wp-json/ae/v1/headless/skills/${encodeURIComponent(skillPath.slug)}`
|
|
35
|
+
|
|
36
|
+
const skill = await getJson(bySlugUrl)
|
|
37
|
+
if (!skill?.slug) {
|
|
38
|
+
throw new Error(`No se encontró la skill "${skillPath.path}" en Alpaki.`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (skillPath.category && skill.sourcePath && skill.sourcePath !== skillPath.path) {
|
|
42
|
+
// Si el usuario pasó categoria/slug, validamos que coincida con la fuente.
|
|
43
|
+
if (!String(skill.sourcePath).endsWith(`/${skillPath.slug}`)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`La skill "${skill.slug}" existe, pero no coincide con "${skillPath.path}". Fuente: ${skill.sourcePath}`,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return skill
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {{ search?: string, category?: string, page?: number, perPage?: number, apiBase?: string }} filters
|
|
55
|
+
*/
|
|
56
|
+
export async function fetchSkillsCatalog(filters = {}) {
|
|
57
|
+
const apiBase = normalizeApiBase(filters.apiBase)
|
|
58
|
+
const params = new URLSearchParams()
|
|
59
|
+
if (filters.search) params.set('search', filters.search)
|
|
60
|
+
if (filters.category) params.set('category', filters.category)
|
|
61
|
+
if (filters.page) params.set('page', String(filters.page))
|
|
62
|
+
if (filters.perPage) params.set('perPage', String(filters.perPage))
|
|
63
|
+
const query = params.toString()
|
|
64
|
+
const url = `${apiBase}/wp-json/ae/v1/headless/skills${query ? `?${query}` : ''}`
|
|
65
|
+
return getJson(url)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {string} [apiBase]
|
|
70
|
+
*/
|
|
71
|
+
function normalizeApiBase(apiBase) {
|
|
72
|
+
const fromEnv = process.env.ALPAKI_API_BASE || process.env.ALPAPI_API_BASE || ''
|
|
73
|
+
const base = String(apiBase || fromEnv || DEFAULT_API_BASE).replace(/\/$/, '')
|
|
74
|
+
return base
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} url
|
|
79
|
+
*/
|
|
80
|
+
async function getJson(url) {
|
|
81
|
+
let response
|
|
82
|
+
try {
|
|
83
|
+
response = await fetch(url, {
|
|
84
|
+
headers: {
|
|
85
|
+
Accept: 'application/json',
|
|
86
|
+
'User-Agent': 'alpaki-skills-cli/1.0',
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
91
|
+
throw new Error(`No se pudo conectar con Alpaki (${url}): ${reason}`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const text = await response.text()
|
|
95
|
+
let data
|
|
96
|
+
try {
|
|
97
|
+
data = text ? JSON.parse(text) : null
|
|
98
|
+
} catch {
|
|
99
|
+
throw new Error(`Respuesta inválida de Alpaki (${response.status}).`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const message = data?.message || `HTTP ${response.status}`
|
|
104
|
+
throw new Error(`Alpaki respondió error: ${message}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return data
|
|
108
|
+
}
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fetchSkill, fetchSkillsCatalog, resolveSkillPath } from './api.js'
|
|
4
|
+
import { printHelp, printSkillInstalled, printCatalog } from './print.js'
|
|
5
|
+
import { resolveInstallDir } from './paths.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string[]} argv
|
|
9
|
+
*/
|
|
10
|
+
export async function runCli(argv) {
|
|
11
|
+
const args = parseArgs(argv)
|
|
12
|
+
|
|
13
|
+
if (args.help || (!args.skill && !args.list && !args.search)) {
|
|
14
|
+
printHelp()
|
|
15
|
+
if (!args.help && !args.skill && !args.list && !args.search) {
|
|
16
|
+
process.exitCode = 1
|
|
17
|
+
}
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (args.list || args.search) {
|
|
22
|
+
const catalog = await fetchSkillsCatalog({
|
|
23
|
+
search: args.search || '',
|
|
24
|
+
category: args.category || '',
|
|
25
|
+
page: args.page,
|
|
26
|
+
perPage: args.perPage,
|
|
27
|
+
apiBase: args.api,
|
|
28
|
+
})
|
|
29
|
+
printCatalog(catalog, { search: args.search || '' })
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const skillPath = resolveSkillPath(args.skill)
|
|
34
|
+
const skill = await fetchSkill(skillPath, { apiBase: args.api })
|
|
35
|
+
const installRoot = resolveInstallDir({
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
target: args.dir,
|
|
38
|
+
global: args.global,
|
|
39
|
+
tool: args.tool,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const folderName = skill.slug || skillPath.slug
|
|
43
|
+
const skillDir = path.join(installRoot, folderName)
|
|
44
|
+
const skillFile = path.join(skillDir, 'SKILL.md')
|
|
45
|
+
const body = buildSkillMarkdown(skill)
|
|
46
|
+
|
|
47
|
+
await mkdir(skillDir, { recursive: true })
|
|
48
|
+
await writeFile(skillFile, body, 'utf8')
|
|
49
|
+
|
|
50
|
+
printSkillInstalled({
|
|
51
|
+
skill,
|
|
52
|
+
skillPath,
|
|
53
|
+
skillFile,
|
|
54
|
+
installRoot,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string[]} argv
|
|
60
|
+
*/
|
|
61
|
+
function parseArgs(argv) {
|
|
62
|
+
/** @type {Record<string, any>} */
|
|
63
|
+
const out = {
|
|
64
|
+
help: false,
|
|
65
|
+
list: false,
|
|
66
|
+
global: false,
|
|
67
|
+
skill: '',
|
|
68
|
+
search: '',
|
|
69
|
+
category: '',
|
|
70
|
+
dir: '',
|
|
71
|
+
tool: 'cursor',
|
|
72
|
+
api: '',
|
|
73
|
+
page: 1,
|
|
74
|
+
perPage: 24,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
78
|
+
const token = argv[i]
|
|
79
|
+
const next = argv[i + 1]
|
|
80
|
+
|
|
81
|
+
if (token === '-h' || token === '--help') {
|
|
82
|
+
out.help = true
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
if (token === 'list' || token === '--list') {
|
|
86
|
+
out.list = true
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
if (token === 'search' || token === '--search') {
|
|
90
|
+
out.search = next || ''
|
|
91
|
+
i += 1
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
if (token === '--skill' || token === '-s') {
|
|
95
|
+
out.skill = next || ''
|
|
96
|
+
i += 1
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
if (token === '--dir' || token === '-d') {
|
|
100
|
+
out.dir = next || ''
|
|
101
|
+
i += 1
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
if (token === '--tool' || token === '-t') {
|
|
105
|
+
out.tool = next || 'cursor'
|
|
106
|
+
i += 1
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (token === '--api') {
|
|
110
|
+
out.api = next || ''
|
|
111
|
+
i += 1
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (token === '--category' || token === '-c') {
|
|
115
|
+
out.category = next || ''
|
|
116
|
+
i += 1
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (token === '--page') {
|
|
120
|
+
out.page = Number(next) || 1
|
|
121
|
+
i += 1
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
if (token === '--per-page') {
|
|
125
|
+
out.perPage = Number(next) || 24
|
|
126
|
+
i += 1
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
if (token === '--global' || token === '-g') {
|
|
130
|
+
out.global = true
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
if (!token.startsWith('-') && !out.skill && token.includes('/')) {
|
|
134
|
+
out.skill = token
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return out
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {{ title?: string, slug?: string, excerpt?: string, content?: string, sourceName?: string, category?: { name?: string, slug?: string } | null, license?: string }} skill
|
|
144
|
+
*/
|
|
145
|
+
function buildSkillMarkdown(skill) {
|
|
146
|
+
const name = skill.sourceName || skill.slug || skill.title || 'skill'
|
|
147
|
+
const description = (skill.excerpt || skill.title || name).replace(/\s+/g, ' ').trim()
|
|
148
|
+
const lines = [
|
|
149
|
+
'---',
|
|
150
|
+
`name: ${yamlQuote(name)}`,
|
|
151
|
+
`description: ${yamlQuote(description)}`,
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
if (skill.license) {
|
|
155
|
+
lines.push(`license: ${yamlQuote(skill.license)}`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
lines.push('---', '')
|
|
159
|
+
lines.push((skill.content || '').trim(), '')
|
|
160
|
+
|
|
161
|
+
return `${lines.join('\n').trim()}\n`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {string} value
|
|
166
|
+
*/
|
|
167
|
+
function yamlQuote(value) {
|
|
168
|
+
const safe = String(value).replace(/"/g, '\\"')
|
|
169
|
+
return `"${safe}"`
|
|
170
|
+
}
|
package/lib/paths.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os from 'node:os'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {{ cwd: string, target?: string, global?: boolean, tool?: string }} options
|
|
6
|
+
*/
|
|
7
|
+
export function resolveInstallDir(options) {
|
|
8
|
+
if (options.target) {
|
|
9
|
+
return path.resolve(options.cwd, options.target)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const tool = String(options.tool || 'cursor').toLowerCase()
|
|
13
|
+
const relative =
|
|
14
|
+
tool === 'claude' || tool === 'claude-code'
|
|
15
|
+
? path.join('.claude', 'skills')
|
|
16
|
+
: path.join('.cursor', 'skills')
|
|
17
|
+
|
|
18
|
+
if (options.global) {
|
|
19
|
+
return path.join(os.homedir(), relative)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return path.join(options.cwd, relative)
|
|
23
|
+
}
|
package/lib/print.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export function printHelp() {
|
|
2
|
+
console.log(`
|
|
3
|
+
alpaki-skills — instala skills de Alpaki en Cursor / Claude Code
|
|
4
|
+
|
|
5
|
+
Uso:
|
|
6
|
+
npx alpaki-skills@latest --skill <categoria/slug>
|
|
7
|
+
npx alpaki-skills@latest creative-design/3d-web-experience
|
|
8
|
+
npx alpaki-skills@latest list
|
|
9
|
+
npx alpaki-skills@latest search "frontend"
|
|
10
|
+
|
|
11
|
+
Opciones:
|
|
12
|
+
--skill, -s Ruta categoria/slug (ej. development/api-design)
|
|
13
|
+
--dir, -d Carpeta destino (default: ./.cursor/skills)
|
|
14
|
+
--tool, -t cursor | claude (default: cursor)
|
|
15
|
+
--global, -g Instalar en el home del usuario
|
|
16
|
+
--api Base URL de la API (default: https://api.alpaki.app)
|
|
17
|
+
--category, -c Filtrar catálogo por categoría
|
|
18
|
+
--page Página del catálogo
|
|
19
|
+
--per-page Items por página
|
|
20
|
+
--help, -h Mostrar ayuda
|
|
21
|
+
|
|
22
|
+
Ejemplos:
|
|
23
|
+
npx alpaki-skills@latest --skill creative-design/3d-web-experience
|
|
24
|
+
npx alpaki-skills@latest --skill git/commit --tool claude
|
|
25
|
+
npx alpaki-skills@latest list --category development
|
|
26
|
+
`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {{ skill: any, skillPath: { path: string }, skillFile: string, installRoot: string }} payload
|
|
31
|
+
*/
|
|
32
|
+
export function printSkillInstalled(payload) {
|
|
33
|
+
const { skill, skillPath, skillFile, installRoot } = payload
|
|
34
|
+
console.log(`
|
|
35
|
+
✓ Skill instalada
|
|
36
|
+
|
|
37
|
+
${skill.title || skill.slug}
|
|
38
|
+
Origen: ${skillPath.path}
|
|
39
|
+
Destino: ${skillFile}
|
|
40
|
+
Carpeta: ${installRoot}
|
|
41
|
+
|
|
42
|
+
Ábrela en Cursor como skill local o cópiala a tu flujo de agente.
|
|
43
|
+
Catálogo: https://alpaki.app/skills
|
|
44
|
+
`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {{ items?: any[], total?: number, page?: number, totalPages?: number, categories?: any[] }} catalog
|
|
49
|
+
* @param {{ search?: string }} [meta]
|
|
50
|
+
*/
|
|
51
|
+
export function printCatalog(catalog, meta = {}) {
|
|
52
|
+
const items = catalog.items || []
|
|
53
|
+
const total = catalog.total ?? items.length
|
|
54
|
+
const header = meta.search ? `Resultados para "${meta.search}"` : 'Catálogo Alpaki Skills'
|
|
55
|
+
|
|
56
|
+
console.log(`\n${header} (${total})\n`)
|
|
57
|
+
|
|
58
|
+
if (!items.length) {
|
|
59
|
+
console.log('No hay skills con ese filtro.\n')
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const item of items) {
|
|
64
|
+
const pathLabel = item.sourcePath || item.slug
|
|
65
|
+
const category = item.category?.name || 'Skill'
|
|
66
|
+
console.log(`- ${pathLabel}`)
|
|
67
|
+
console.log(` ${item.title} · ${category}`)
|
|
68
|
+
if (item.excerpt) {
|
|
69
|
+
const excerpt = String(item.excerpt).replace(/\s+/g, ' ').slice(0, 110)
|
|
70
|
+
console.log(` ${excerpt}${item.excerpt.length > 110 ? '…' : ''}`)
|
|
71
|
+
}
|
|
72
|
+
console.log('')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if ((catalog.totalPages || 1) > 1) {
|
|
76
|
+
console.log(`Página ${catalog.page || 1} de ${catalog.totalPages}. Usa --page para ver más.\n`)
|
|
77
|
+
}
|
|
78
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alpaki-skills",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI para instalar skills de Alpaki en Cursor / Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"alpaki-skills": "bin/alpaki-skills.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"lib",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node ./bin/alpaki-skills.js",
|
|
17
|
+
"test": "node --test test/*.test.js"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"alpaki",
|
|
24
|
+
"skills",
|
|
25
|
+
"cursor",
|
|
26
|
+
"claude",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"author": "Alpaki",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/danielandrehz/alpaki-skills.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/danielandrehz/alpaki-skills/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://alpaki.app/skills"
|
|
39
|
+
}
|