@basetisia/skill-manager 0.1.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 +242 -0
- package/bin/index.js +119 -0
- package/package.json +27 -0
- package/scripts/publish.sh +37 -0
- package/scripts/setup.sh +50 -0
- package/src/adapters/adapters.test.js +175 -0
- package/src/adapters/claude.js +26 -0
- package/src/adapters/codex.js +31 -0
- package/src/adapters/cursor.js +46 -0
- package/src/adapters/gemini.js +26 -0
- package/src/adapters/index.js +45 -0
- package/src/adapters/shared.js +48 -0
- package/src/adapters/windsurf.js +82 -0
- package/src/commands/detect.js +135 -0
- package/src/commands/init.js +130 -0
- package/src/commands/install.js +105 -0
- package/src/commands/list.js +69 -0
- package/src/commands/login.js +29 -0
- package/src/commands/logout.js +13 -0
- package/src/commands/status.js +56 -0
- package/src/commands/whoami.js +29 -0
- package/src/manifest.js +84 -0
- package/src/manifest.test.js +183 -0
- package/src/utils/api.js +39 -0
- package/src/utils/auth.js +287 -0
- package/src/utils/config.js +35 -0
- package/src/utils/fs.js +39 -0
- package/src/utils/gitlab.js +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# @basetis/skill-manager
|
|
2
|
+
|
|
3
|
+
CLI para gestionar skills de IA (prompts reutilizables) e instalarlas en múltiples herramientas de coding: Claude Code, Gemini CLI, Codex CLI, Cursor y Windsurf.
|
|
4
|
+
|
|
5
|
+
Las skills se almacenan en un repositorio GitLab centralizado (`git@git.basetis.com:okr-ia/basetisskills.git`) con un catálogo definido en `manifest.yml`. El CLI descarga las skills desde ese catálogo y las coloca en el formato y ruta correctos para cada herramienta.
|
|
6
|
+
|
|
7
|
+
## Prerequisitos
|
|
8
|
+
|
|
9
|
+
- **Node.js >= 24** — verifica con `node --version`
|
|
10
|
+
- **Token de acceso personal de GitLab** con scope `read_package_registry` en `git.basetis.com`
|
|
11
|
+
|
|
12
|
+
## Setup para developer nuevo
|
|
13
|
+
|
|
14
|
+
La forma más rápida de instalar es ejecutar el script de setup:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bash <(curl -fsSL https://git.basetis.com/okr-ia/basetisskills/-/raw/main/cli/scripts/setup.sh)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
El script te pedirá tu token personal de GitLab, configurará npm y instalará el CLI globalmente.
|
|
21
|
+
|
|
22
|
+
### Generar el token de GitLab
|
|
23
|
+
|
|
24
|
+
1. Ve a [git.basetis.com → Settings → Access Tokens](https://git.basetis.com/-/user_settings/personal_access_tokens)
|
|
25
|
+
2. Crea un token con el scope: **read_package_registry**
|
|
26
|
+
3. Copia el token (empieza por `glpat-...`)
|
|
27
|
+
|
|
28
|
+
### Instalación manual
|
|
29
|
+
|
|
30
|
+
Si prefieres configurarlo a mano, añade a tu `~/.npmrc`:
|
|
31
|
+
|
|
32
|
+
```ini
|
|
33
|
+
@basetis:registry=https://git.basetis.com/api/v4/packages/npm/
|
|
34
|
+
//git.basetis.com/api/v4/packages/npm/:_authToken=TU_GITLAB_TOKEN
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Y luego:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g @basetis/skill-manager
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Verifica la instalación:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
skill-manager --version
|
|
47
|
+
# 0.1.0
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Primeros pasos
|
|
51
|
+
|
|
52
|
+
### 1. Configurar el repositorio de skills
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
skill-manager init
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
? Tipo de repositorio de skills: › GitLab
|
|
60
|
+
? URL base del servidor: › https://git.basetis.com
|
|
61
|
+
? Project ID (numérico) o ruta (grupo/repo): › okr-ia/basetisskills
|
|
62
|
+
? Token de acceso personal: › ****
|
|
63
|
+
? Rama del repositorio: › main
|
|
64
|
+
? Herramienta de IA por defecto: › Claude Code
|
|
65
|
+
|
|
66
|
+
✔ Conexión verificada correctamente.
|
|
67
|
+
✔ Configuración guardada correctamente.
|
|
68
|
+
→ ~/.skill-manager/config.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 2. Explorar el catálogo
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
skill-manager list
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
User Skills
|
|
79
|
+
────────────────────────────────────────
|
|
80
|
+
user/development/kotlin-jvm v1.0.0
|
|
81
|
+
user/development/react v1.0.0
|
|
82
|
+
|
|
83
|
+
Internal Skills
|
|
84
|
+
────────────────────────────────────────
|
|
85
|
+
internal/global v2.0.0
|
|
86
|
+
|
|
87
|
+
External Skills
|
|
88
|
+
────────────────────────────────────────
|
|
89
|
+
external/sector/pharma v1.0.0
|
|
90
|
+
external/client/alexion v1.2.0 ← external/sector/pharma
|
|
91
|
+
external/project/alexion-cockpit v2.0.0 ← external/client/alexion
|
|
92
|
+
|
|
93
|
+
Total: 5 skills
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Filtrar por nombre:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
skill-manager list --filter alexion
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Instalar una skill
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
skill-manager install external/project/alexion-cockpit
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
✔ Cadena resuelta: 3 skill(s).
|
|
110
|
+
|
|
111
|
+
Skills a instalar (en orden):
|
|
112
|
+
├─ external/sector/pharma v1.0.0
|
|
113
|
+
├─ external/client/alexion v1.2.0
|
|
114
|
+
└─ external/project/alexion-cockpit v2.0.0
|
|
115
|
+
|
|
116
|
+
? ¿En qué herramientas quieres instalar? › Claude Code, Cursor
|
|
117
|
+
? ¿Dónde instalar las skills? › Global (solo para ti, en ~/)
|
|
118
|
+
|
|
119
|
+
✔ Instalada external/sector/pharma
|
|
120
|
+
✔ Instalada external/client/alexion
|
|
121
|
+
✔ Instalada external/project/alexion-cockpit
|
|
122
|
+
|
|
123
|
+
✔ 3 skill(s) instalada(s) en Claude Code y Cursor (personal)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Instalación directa sin prompts:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
skill-manager install external/client/alexion --tool claude,windsurf --scope global
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Ver estado de las skills instaladas
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
skill-manager status
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Claude Code
|
|
140
|
+
──────────────────────────────────────────────────
|
|
141
|
+
[Global]
|
|
142
|
+
external/sector/pharma 1.0.0 ✓ al día
|
|
143
|
+
external/client/alexion 1.1.0 ⬆ 1.2.0 disponible
|
|
144
|
+
external/project/alexion-cockpit 2.0.0 ✓ al día
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 5. Detectar skills recomendadas automáticamente
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
cd mi-proyecto-kotlin/
|
|
151
|
+
skill-manager detect
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
✔ Análisis completo.
|
|
156
|
+
|
|
157
|
+
Skills recomendadas:
|
|
158
|
+
|
|
159
|
+
internal/global v2.0.0
|
|
160
|
+
→ Skill base recomendada para todos los proyectos.
|
|
161
|
+
user/development/kotlin-jvm v1.0.0
|
|
162
|
+
→ Detectado: kotlin, jvm, gradle en el proyecto.
|
|
163
|
+
|
|
164
|
+
? ¿Instalar las skills recomendadas? › Sí
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Referencia de comandos
|
|
168
|
+
|
|
169
|
+
| Comando | Argumentos | Descripción |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `skill-manager init` | — | Configura el repositorio de skills y herramienta por defecto |
|
|
172
|
+
| `skill-manager list` | `--filter <texto>` | Lista skills del catálogo, agrupadas por sección |
|
|
173
|
+
| `skill-manager install <ruta>` | `--tool <herramienta>` `--scope <global\|project>` | Instala una skill y toda su cadena de dependencias |
|
|
174
|
+
| `skill-manager status` | — | Compara versiones instaladas vs catálogo para cada herramienta |
|
|
175
|
+
| `skill-manager detect` | — | Analiza el proyecto actual y recomienda skills relevantes |
|
|
176
|
+
|
|
177
|
+
### Opciones globales
|
|
178
|
+
|
|
179
|
+
| Opción | Descripción |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `--version`, `-V` | Muestra la versión del CLI |
|
|
182
|
+
| `--help`, `-h` | Muestra la ayuda general o de un comando |
|
|
183
|
+
|
|
184
|
+
## Herramientas soportadas
|
|
185
|
+
|
|
186
|
+
| Herramienta | Personal (global) | Proyecto (local) |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| Claude Code | `~/.claude/skills/{path}/SKILL.md` | `.claude/skills/{path}/SKILL.md` |
|
|
189
|
+
| Gemini CLI | `~/.gemini/skills/{path}/SKILL.md` | `.agents/skills/{path}/SKILL.md` |
|
|
190
|
+
| Codex CLI | `~/.codex/{name}/SKILL.md` | `.agents/skills/{path}/SKILL.md` |
|
|
191
|
+
| Cursor | `~/.cursor/rules/{name}.mdc` | `.cursor/rules/{name}.mdc` |
|
|
192
|
+
| Windsurf | `~/.windsurf/global_rules.md` (append) | `.windsurf/rules/{name}.md` |
|
|
193
|
+
|
|
194
|
+
## Cómo añadir soporte para una nueva herramienta
|
|
195
|
+
|
|
196
|
+
1. Crea un nuevo archivo en `src/adapters/<herramienta>.js` exportando:
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
export const toolName = "mi-herramienta";
|
|
200
|
+
export const displayName = "Mi Herramienta";
|
|
201
|
+
|
|
202
|
+
export function getInstallPath(skillPath, scope) {
|
|
203
|
+
// Devuelve la ruta donde se instalará el SKILL.md
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function install(skillPath, skillContent, scope) {
|
|
207
|
+
// Escribe el contenido en la ruta correcta
|
|
208
|
+
// Transforma el formato si es necesario (como Cursor con .mdc)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function getInstalledSkills(scope) {
|
|
212
|
+
// Devuelve [{ path, version }] leyendo los archivos instalados
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
2. Regístralo en `src/adapters/index.js`:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
import * as miHerramienta from "./mi-herramienta.js";
|
|
220
|
+
|
|
221
|
+
const adapters = [claude, gemini, codex, cursor, windsurf, miHerramienta];
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
3. Añade la detección en `detectInstalledTools()` del mismo archivo.
|
|
225
|
+
|
|
226
|
+
4. Escribe tests en `src/adapters/adapters.test.js` siguiendo el patrón existente.
|
|
227
|
+
|
|
228
|
+
## Cómo contribuir al catálogo de skills
|
|
229
|
+
|
|
230
|
+
Las skills se gestionan en el repositorio [okr-ia/basetisskills](https://git.basetis.com/okr-ia/basetisskills).
|
|
231
|
+
|
|
232
|
+
Consulta el archivo `CONTRIBUTING.md` de ese repositorio para conocer:
|
|
233
|
+
|
|
234
|
+
- Estructura de carpetas y convenciones de naming
|
|
235
|
+
- Formato del `manifest.yml`
|
|
236
|
+
- Cómo escribir un `SKILL.md` con frontmatter válido
|
|
237
|
+
- Flujo de review y merge para nuevas skills
|
|
238
|
+
- Cómo usar `parent` y `upstream` para cadenas de herencia
|
|
239
|
+
|
|
240
|
+
## Licencia
|
|
241
|
+
|
|
242
|
+
Uso interno Basetis.
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { init } from "../src/commands/init.js";
|
|
5
|
+
import { list } from "../src/commands/list.js";
|
|
6
|
+
import { install } from "../src/commands/install.js";
|
|
7
|
+
import { status } from "../src/commands/status.js";
|
|
8
|
+
import { detect } from "../src/commands/detect.js";
|
|
9
|
+
import { login } from "../src/commands/login.js";
|
|
10
|
+
import { logout } from "../src/commands/logout.js";
|
|
11
|
+
import { whoami } from "../src/commands/whoami.js";
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name("skill-manager")
|
|
17
|
+
.description("CLI para gestionar skills de IA en tu editor")
|
|
18
|
+
.version("0.1.0");
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command("init")
|
|
22
|
+
.description("Configura el repositorio de skills y la herramienta por defecto")
|
|
23
|
+
.action(async () => {
|
|
24
|
+
try {
|
|
25
|
+
await init();
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(err.message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command("list")
|
|
34
|
+
.description("Lista todas las skills disponibles en el catálogo")
|
|
35
|
+
.option("-f, --filter <texto>", "Filtra skills por nombre")
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
try {
|
|
38
|
+
await list(options);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error(err.message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.command("install <ruta>")
|
|
47
|
+
.description("Instala una skill y sus dependencias (cadena de parents)")
|
|
48
|
+
.option("-t, --tool <herramienta>", "Herramienta(s) destino, separadas por coma (claude,cursor,gemini,codex,windsurf)")
|
|
49
|
+
.option("-s, --scope <ámbito>", "Ámbito de instalación: global o project")
|
|
50
|
+
.action(async (ruta, options) => {
|
|
51
|
+
try {
|
|
52
|
+
await install(ruta, options);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(err.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command("status")
|
|
61
|
+
.description("Muestra el estado de las skills instaladas vs el catálogo")
|
|
62
|
+
.action(async () => {
|
|
63
|
+
try {
|
|
64
|
+
await status();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(err.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
program
|
|
72
|
+
.command("detect")
|
|
73
|
+
.description("Analiza el proyecto actual y recomienda skills relevantes")
|
|
74
|
+
.action(async () => {
|
|
75
|
+
try {
|
|
76
|
+
await detect();
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(err.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
program
|
|
84
|
+
.command("login")
|
|
85
|
+
.description("Inicia sesión con tu cuenta de Basetis (Google)")
|
|
86
|
+
.action(async () => {
|
|
87
|
+
try {
|
|
88
|
+
await login();
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(err.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
program
|
|
96
|
+
.command("logout")
|
|
97
|
+
.description("Cierra la sesión actual")
|
|
98
|
+
.action(async () => {
|
|
99
|
+
try {
|
|
100
|
+
await logout();
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(err.message);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
program
|
|
108
|
+
.command("whoami")
|
|
109
|
+
.description("Muestra el usuario autenticado")
|
|
110
|
+
.action(async () => {
|
|
111
|
+
try {
|
|
112
|
+
await whoami();
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error(err.message);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@basetisia/skill-manager",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to manage AI coding skills across editors",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"skill-manager": "./bin/index.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test",
|
|
14
|
+
"start": "node bin/index.js"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"registry": "https://registry.npmjs.org/",
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"chalk": "^5",
|
|
22
|
+
"commander": "^13",
|
|
23
|
+
"inquirer": "^12",
|
|
24
|
+
"js-yaml": "^4",
|
|
25
|
+
"ora": "^8"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Directorio raíz del CLI (un nivel arriba de scripts/)
|
|
5
|
+
CLI_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
6
|
+
|
|
7
|
+
echo "📦 @basetis/skill-manager — publish (GitLab Package Registry)"
|
|
8
|
+
echo "───────────────────────────────────────────────────────────────"
|
|
9
|
+
|
|
10
|
+
# 1. Verificar token de GitLab
|
|
11
|
+
if [[ -z "${GITLAB_TOKEN:-}" ]]; then
|
|
12
|
+
echo "❌ Error: Define GITLAB_TOKEN antes de publicar."
|
|
13
|
+
echo " Ejemplo: export GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx"
|
|
14
|
+
echo ""
|
|
15
|
+
echo " Genera uno en: git.basetis.com → Settings → Access Tokens"
|
|
16
|
+
echo " Scopes necesarios: api"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
echo "✔ GITLAB_TOKEN configurado."
|
|
20
|
+
|
|
21
|
+
# 2. Ejecutar tests
|
|
22
|
+
echo "⏳ Ejecutando tests..."
|
|
23
|
+
cd "$CLI_DIR"
|
|
24
|
+
npm run test
|
|
25
|
+
echo "✔ Tests pasados."
|
|
26
|
+
|
|
27
|
+
# 3. Publicar paquete
|
|
28
|
+
echo "⏳ Publicando en GitLab Package Registry..."
|
|
29
|
+
npm publish
|
|
30
|
+
|
|
31
|
+
# 4. Confirmar publicación
|
|
32
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
33
|
+
echo ""
|
|
34
|
+
echo "───────────────────────────────────────────────────────────────"
|
|
35
|
+
echo "✔ Publicado @basetis/skill-manager@${VERSION}"
|
|
36
|
+
echo " Registry: https://git.basetis.com/okr-ia/basetisskills/-/packages"
|
|
37
|
+
echo " Instalar: npm install -g @basetis/skill-manager"
|
package/scripts/setup.sh
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
echo "🔧 Setup @basetis/skill-manager"
|
|
5
|
+
echo "────────────────────────────────"
|
|
6
|
+
echo ""
|
|
7
|
+
echo "Necesitas un token personal de GitLab con scope read_package_registry."
|
|
8
|
+
echo ""
|
|
9
|
+
echo " 1. Ve a: https://git.basetis.com/-/user_settings/personal_access_tokens"
|
|
10
|
+
echo " 2. Crea un token con el scope: read_package_registry"
|
|
11
|
+
echo " 3. Copia el token (empieza por glpat-...)"
|
|
12
|
+
echo ""
|
|
13
|
+
|
|
14
|
+
read -rp "Pega tu GitLab token: " GITLAB_TOKEN
|
|
15
|
+
|
|
16
|
+
if [[ -z "$GITLAB_TOKEN" ]]; then
|
|
17
|
+
echo "❌ Token vacío. Abortando."
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Escribir configuración en ~/.npmrc
|
|
22
|
+
NPMRC="$HOME/.npmrc"
|
|
23
|
+
|
|
24
|
+
# Eliminar líneas anteriores de @basetis si existen
|
|
25
|
+
if [[ -f "$NPMRC" ]]; then
|
|
26
|
+
grep -v '@basetis:registry' "$NPMRC" | grep -v '//git.basetis.com/api/v4/packages/npm/:_authToken' > "$NPMRC.tmp" || true
|
|
27
|
+
mv "$NPMRC.tmp" "$NPMRC"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
echo "@basetis:registry=https://git.basetis.com/api/v4/packages/npm/" >> "$NPMRC"
|
|
31
|
+
echo "//git.basetis.com/api/v4/packages/npm/:_authToken=${GITLAB_TOKEN}" >> "$NPMRC"
|
|
32
|
+
|
|
33
|
+
echo "✔ ~/.npmrc configurado."
|
|
34
|
+
|
|
35
|
+
# Instalar el CLI globalmente
|
|
36
|
+
echo "⏳ Instalando @basetis/skill-manager..."
|
|
37
|
+
npm install -g @basetis/skill-manager
|
|
38
|
+
|
|
39
|
+
# Verificar instalación
|
|
40
|
+
echo ""
|
|
41
|
+
if skill-manager --version &>/dev/null; then
|
|
42
|
+
VERSION=$(skill-manager --version)
|
|
43
|
+
echo "────────────────────────────────"
|
|
44
|
+
echo "✔ skill-manager ${VERSION} instalado correctamente."
|
|
45
|
+
echo " Ejecuta: skill-manager init"
|
|
46
|
+
else
|
|
47
|
+
echo "❌ Error: skill-manager no se encontró en el PATH."
|
|
48
|
+
echo " Verifica que la carpeta global de npm está en tu PATH."
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, rm, readFile, realpath } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
const SKILL_CONTENT = `---
|
|
8
|
+
name: test-skill
|
|
9
|
+
version: "1.0.0"
|
|
10
|
+
description: A test skill
|
|
11
|
+
---
|
|
12
|
+
You are a test skill.
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
let tmpHome, tmpCwd, origHome, origCwd;
|
|
16
|
+
|
|
17
|
+
async function setup() {
|
|
18
|
+
tmpHome = await realpath(await mkdtemp(join(tmpdir(), "sm-home-")));
|
|
19
|
+
tmpCwd = await realpath(await mkdtemp(join(tmpdir(), "sm-cwd-")));
|
|
20
|
+
origHome = process.env.HOME;
|
|
21
|
+
origCwd = process.cwd();
|
|
22
|
+
process.env.HOME = tmpHome;
|
|
23
|
+
process.chdir(tmpCwd);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function teardown() {
|
|
27
|
+
process.env.HOME = origHome;
|
|
28
|
+
process.chdir(origCwd);
|
|
29
|
+
await rm(tmpHome, { recursive: true, force: true });
|
|
30
|
+
await rm(tmpCwd, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Re-import adapters fresh so homeDir/cwdDir pick up the new env
|
|
34
|
+
async function loadAdapter(name) {
|
|
35
|
+
const mod = await import(`./${name}.js?t=${Date.now()}`);
|
|
36
|
+
return mod;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("claude adapter", () => {
|
|
40
|
+
beforeEach(setup);
|
|
41
|
+
afterEach(teardown);
|
|
42
|
+
|
|
43
|
+
it("installs to personal scope at ~/.claude/skills/", async () => {
|
|
44
|
+
const claude = await loadAdapter("claude");
|
|
45
|
+
const dest = await claude.install("external/sector/pharma", SKILL_CONTENT, "personal");
|
|
46
|
+
assert.equal(dest, join(tmpHome, ".claude", "skills", "external", "sector", "pharma", "SKILL.md"));
|
|
47
|
+
const content = await readFile(dest, "utf-8");
|
|
48
|
+
assert.ok(content.includes("test skill"));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("installs to project scope at .claude/skills/", async () => {
|
|
52
|
+
const claude = await loadAdapter("claude");
|
|
53
|
+
const dest = await claude.install("internal/tooling/eslint", SKILL_CONTENT, "project");
|
|
54
|
+
assert.equal(dest, join(tmpCwd, ".claude", "skills", "internal", "tooling", "eslint", "SKILL.md"));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("getInstallPath returns correct path", async () => {
|
|
58
|
+
const claude = await loadAdapter("claude");
|
|
59
|
+
const p = claude.getInstallPath("a/b/c", "personal");
|
|
60
|
+
assert.ok(p.endsWith(join(".claude", "skills", "a", "b", "c", "SKILL.md")));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("getInstalledSkills reads back installed skills", async () => {
|
|
64
|
+
const claude = await loadAdapter("claude");
|
|
65
|
+
await claude.install("sector/pharma", SKILL_CONTENT, "personal");
|
|
66
|
+
const skills = await claude.getInstalledSkills("personal");
|
|
67
|
+
assert.equal(skills.length, 1);
|
|
68
|
+
assert.equal(skills[0].path, "sector/pharma");
|
|
69
|
+
assert.equal(skills[0].version, "1.0.0");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("gemini adapter", () => {
|
|
74
|
+
beforeEach(setup);
|
|
75
|
+
afterEach(teardown);
|
|
76
|
+
|
|
77
|
+
it("installs to personal scope at ~/.gemini/skills/", async () => {
|
|
78
|
+
const gemini = await loadAdapter("gemini");
|
|
79
|
+
const dest = await gemini.install("sector/pharma", SKILL_CONTENT, "personal");
|
|
80
|
+
assert.equal(dest, join(tmpHome, ".gemini", "skills", "sector", "pharma", "SKILL.md"));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("installs to project scope at .agents/skills/", async () => {
|
|
84
|
+
const gemini = await loadAdapter("gemini");
|
|
85
|
+
const dest = await gemini.install("sector/pharma", SKILL_CONTENT, "project");
|
|
86
|
+
assert.equal(dest, join(tmpCwd, ".agents", "skills", "sector", "pharma", "SKILL.md"));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("codex adapter", () => {
|
|
91
|
+
beforeEach(setup);
|
|
92
|
+
afterEach(teardown);
|
|
93
|
+
|
|
94
|
+
it("installs to personal scope at ~/.codex/{skillName}/", async () => {
|
|
95
|
+
const codex = await loadAdapter("codex");
|
|
96
|
+
const dest = await codex.install("external/sector/pharma", SKILL_CONTENT, "personal");
|
|
97
|
+
assert.equal(dest, join(tmpHome, ".codex", "pharma", "SKILL.md"));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("installs to project scope at .agents/skills/", async () => {
|
|
101
|
+
const codex = await loadAdapter("codex");
|
|
102
|
+
const dest = await codex.install("external/sector/pharma", SKILL_CONTENT, "project");
|
|
103
|
+
assert.equal(dest, join(tmpCwd, ".agents", "skills", "external", "sector", "pharma", "SKILL.md"));
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("cursor adapter", () => {
|
|
108
|
+
beforeEach(setup);
|
|
109
|
+
afterEach(teardown);
|
|
110
|
+
|
|
111
|
+
it("installs to personal scope as .mdc with cursor frontmatter", async () => {
|
|
112
|
+
const cursor = await loadAdapter("cursor");
|
|
113
|
+
const dest = await cursor.install("external/sector/pharma", SKILL_CONTENT, "personal");
|
|
114
|
+
assert.equal(dest, join(tmpHome, ".cursor", "rules", "pharma.mdc"));
|
|
115
|
+
const content = await readFile(dest, "utf-8");
|
|
116
|
+
assert.ok(content.startsWith("---\ndescription: A test skill\nalwaysApply: false\n---\n"));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("installs to project scope at .cursor/rules/", async () => {
|
|
120
|
+
const cursor = await loadAdapter("cursor");
|
|
121
|
+
const dest = await cursor.install("sector/pharma", SKILL_CONTENT, "project");
|
|
122
|
+
assert.equal(dest, join(tmpCwd, ".cursor", "rules", "pharma.mdc"));
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("windsurf adapter", () => {
|
|
127
|
+
beforeEach(setup);
|
|
128
|
+
afterEach(teardown);
|
|
129
|
+
|
|
130
|
+
it("installs to personal scope by appending to global_rules.md", async () => {
|
|
131
|
+
const windsurf = await loadAdapter("windsurf");
|
|
132
|
+
const dest = await windsurf.install("sector/pharma", SKILL_CONTENT, "personal");
|
|
133
|
+
assert.equal(dest, join(tmpHome, ".windsurf", "global_rules.md"));
|
|
134
|
+
const content = await readFile(dest, "utf-8");
|
|
135
|
+
assert.ok(content.includes("## pharma"));
|
|
136
|
+
assert.ok(content.includes("test skill"));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("appends multiple skills without overwriting", async () => {
|
|
140
|
+
const windsurf = await loadAdapter("windsurf");
|
|
141
|
+
await windsurf.install("sector/pharma", SKILL_CONTENT, "personal");
|
|
142
|
+
await windsurf.install("sector/tech", "---\nname: tech\nversion: \"2.0.0\"\n---\nTech skill.", "personal");
|
|
143
|
+
const content = await readFile(join(tmpHome, ".windsurf", "global_rules.md"), "utf-8");
|
|
144
|
+
assert.ok(content.includes("## pharma"));
|
|
145
|
+
assert.ok(content.includes("## tech"));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("installs to project scope at .windsurf/rules/", async () => {
|
|
149
|
+
const windsurf = await loadAdapter("windsurf");
|
|
150
|
+
const dest = await windsurf.install("sector/pharma", SKILL_CONTENT, "project");
|
|
151
|
+
assert.equal(dest, join(tmpCwd, ".windsurf", "rules", "pharma.md"));
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("adapter index", () => {
|
|
156
|
+
it("getAdapter returns correct adapter", async () => {
|
|
157
|
+
const { getAdapter } = await import("./index.js");
|
|
158
|
+
const claude = getAdapter("claude");
|
|
159
|
+
assert.equal(claude.toolName, "claude");
|
|
160
|
+
assert.equal(claude.displayName, "Claude Code");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("getAdapter throws for unknown tool", async () => {
|
|
164
|
+
const { getAdapter } = await import("./index.js");
|
|
165
|
+
assert.throws(() => getAdapter("unknown"), { message: /no soportada/ });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("getAllAdapters returns 5 adapters", async () => {
|
|
169
|
+
const { getAllAdapters } = await import("./index.js");
|
|
170
|
+
const all = getAllAdapters();
|
|
171
|
+
assert.equal(all.length, 5);
|
|
172
|
+
const names = all.map((a) => a.toolName);
|
|
173
|
+
assert.deepEqual(names, ["claude", "gemini", "codex", "cursor", "windsurf"]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { homeDir, cwdDir, writeFile } from "../utils/fs.js";
|
|
3
|
+
import { scanInstalledSkills } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
export const toolName = "claude";
|
|
6
|
+
export const displayName = "Claude Code";
|
|
7
|
+
|
|
8
|
+
function basePath(scope) {
|
|
9
|
+
return scope === "personal"
|
|
10
|
+
? join(homeDir(), ".claude", "skills")
|
|
11
|
+
: join(cwdDir(), ".claude", "skills");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getInstallPath(skillPath, scope) {
|
|
15
|
+
return join(basePath(scope), skillPath, "SKILL.md");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function install(skillPath, skillContent, scope) {
|
|
19
|
+
const dest = getInstallPath(skillPath, scope);
|
|
20
|
+
await writeFile(dest, skillContent);
|
|
21
|
+
return dest;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getInstalledSkills(scope) {
|
|
25
|
+
return scanInstalledSkills(basePath(scope));
|
|
26
|
+
}
|