@ariasbruno/skillbase 0.1.1 → 1.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/src/core.js CHANGED
@@ -5,9 +5,38 @@ import * as readline from 'node:readline';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
6
  import { execFile } from 'node:child_process';
7
7
  import { promisify } from 'node:util';
8
- import { getGlobalSkillsDir, getProjectRoot, getProjectSkillsDir, PROJECT_AGENTS_DIR, PROJECT_SKILLS_DIR } from './config.js';
8
+ import {
9
+ getGlobalSkillsDir,
10
+ getProjectRoot,
11
+ getProjectSkillsDir,
12
+ PROJECT_AGENTS_DIR,
13
+ PROJECT_SKILLS_DIR,
14
+ exists
15
+ } from './config.js';
9
16
  import { readManifest, removeSkillFromManifest, upsertSkill, writeJson, writeManifest } from './manifest.js';
10
17
  import { detectProjectTechnologies } from './recommendations.js';
18
+ import { t } from './i18n.js';
19
+ import {
20
+ bold,
21
+ dim,
22
+ dim2,
23
+ text,
24
+ cyan,
25
+ green,
26
+ yellow,
27
+ S_DIAMOND,
28
+ S_POINTER,
29
+ S_STEP,
30
+ S_SQUARE,
31
+ S_SQUARE_FILL,
32
+ S_BAR,
33
+ S_BAR_START,
34
+ S_BAR_END,
35
+ H_HIDE_CURSOR,
36
+ H_SHOW_CURSOR,
37
+ H_CLEAR_DOWN,
38
+ H_MOVE_UP
39
+ } from './styles.js';
11
40
  const execFileAsync = promisify(execFile);
12
41
 
13
42
  function nowISO() {
@@ -20,13 +49,13 @@ export async function ensureDir(dir) {
20
49
  await fs.mkdir(dir, { recursive: true });
21
50
  }
22
51
 
23
- async function exists(target) {
24
- try {
25
- await fs.access(target);
26
- return true;
27
- } catch {
28
- return false;
29
- }
52
+ function sanitizeSkillName(name) {
53
+ if (typeof name !== 'string') return '';
54
+
55
+ return name
56
+ .replace(/[\\/]/g, '_')
57
+ .replace(/\.\./g, '')
58
+ .replace(/^[.~/]+/, '');
30
59
  }
31
60
 
32
61
  export async function listGlobalSkills() {
@@ -36,6 +65,13 @@ export async function listGlobalSkills() {
36
65
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
37
66
  }
38
67
 
68
+ export async function listProjectSkills(cwd = process.cwd()) {
69
+ const projectSkillsDir = getProjectSkillsDir(cwd);
70
+ if (!(await exists(projectSkillsDir))) return [];
71
+ const entries = await fs.readdir(projectSkillsDir, { withFileTypes: true });
72
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
73
+ }
74
+
39
75
  async function copyDir(src, dst) {
40
76
  await fs.cp(src, dst, { recursive: true, force: true });
41
77
  }
@@ -61,14 +97,15 @@ function compareVersion(a, b) {
61
97
  }
62
98
 
63
99
  export async function addSkill(skillName, { sym = false, cwd = process.cwd() } = {}) {
64
- const globalPath = path.join(getGlobalSkillsDir(), skillName);
100
+ const safeName = sanitizeSkillName(skillName);
101
+ const globalPath = path.join(getGlobalSkillsDir(), safeName);
65
102
  if (!(await exists(globalPath))) {
66
- throw new Error(`La skill global "${skillName}" no existe en ${getGlobalSkillsDir()}`);
103
+ throw new Error(t('ERR_GLOBAL_NOT_FOUND', { name: skillName, dir: getGlobalSkillsDir() }));
67
104
  }
68
105
 
69
106
  const projectSkillsDir = getProjectSkillsDir(cwd);
70
107
  await ensureDir(projectSkillsDir);
71
- const target = path.join(projectSkillsDir, skillName);
108
+ const target = path.join(projectSkillsDir, safeName);
72
109
 
73
110
  if (await exists(target)) {
74
111
  await fs.rm(target, { recursive: true, force: true });
@@ -93,21 +130,21 @@ export async function addSkillsInteractive({ cwd = process.cwd(), sym = false }
93
130
  const skills = await listGlobalSkills();
94
131
  if (!skills.length) return { selected: [], cancelled: false };
95
132
  const selection = await selectSkillsFromList(skills, {
96
- title: 'Selecciona skills para instalar',
97
- requireTTYMessage: 'La selección interactiva requiere una terminal TTY. Usa: skillbase add <skill>.'
133
+ title: t('UI_SELECT_MULTIPLE'),
134
+ requireTTYMessage: t('UI_REQUIRED_TTY')
98
135
  });
99
136
  if (selection.cancelled) return { selected: [], cancelled: true };
100
137
  const selectedSkills = selection.selected;
101
138
  for (const skill of selectedSkills) {
102
139
  await addSkill(skill, { cwd, sym });
103
140
  }
104
- output.write(`\nInstaladas: ${selectedSkills.join(', ') || 'ninguna'}\n`);
141
+ output.write(`\n${t('INIT_INSTALLED', { list: selectedSkills.join(', ') || t('INIT_NONE') })}\n`);
105
142
  return { selected: selectedSkills, cancelled: false };
106
143
  }
107
144
 
108
- async function selectSkillsFromList(skills, { title, requireTTYMessage } = {}) {
145
+ async function selectSkillsFromList(skills, { title, subtitle, requireTTYMessage } = {}) {
109
146
  if (!input.isTTY || !output.isTTY) {
110
- throw new Error(requireTTYMessage || 'La selección interactiva requiere una terminal TTY.');
147
+ throw new Error(requireTTYMessage || t('UI_REQUIRED_TTY'));
111
148
  }
112
149
 
113
150
  const selected = new Set();
@@ -116,93 +153,96 @@ async function selectSkillsFromList(skills, { title, requireTTYMessage } = {}) {
116
153
  let renderedLines = 0;
117
154
  let firstRender = true;
118
155
 
119
- // Tamaño de página adaptativo (mínimo 5, máximo 15 por defecto)
120
156
  const getPageSize = () => {
121
157
  const terminalRows = output.rows || 24;
122
- const reservedRows = 8; // Header (5) + Footer (2) + Margen (1)
123
- return Math.max(5, Math.min(15, terminalRows - reservedRows));
158
+ const reservedRows = 10;
159
+ return Math.max(5, Math.min(12, terminalRows - reservedRows));
124
160
  };
125
161
 
126
162
  readline.emitKeypressEvents(input);
127
163
  input.resume();
128
164
  if (typeof input.setRawMode === 'function') input.setRawMode(true);
129
165
 
166
+ output.write(H_HIDE_CURSOR);
167
+
130
168
  const render = () => {
131
169
  const pageSize = getPageSize();
132
-
133
- // Ajustar ventana (offset) según el cursor
134
- if (cursor < offset) {
135
- offset = cursor;
136
- } else if (cursor >= offset + pageSize) {
137
- offset = cursor - pageSize + 1;
138
- }
170
+
171
+ if (cursor < offset) offset = cursor;
172
+ else if (cursor >= offset + pageSize) offset = cursor - pageSize + 1;
139
173
 
140
174
  const lines = [
141
- '\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m',
142
- `\x1b[1m${title || 'Selecciona skills'}\x1b[0m`,
143
- '\x1b[2m↑/↓ navegar · espacio seleccionar · enter confirmar · esc cancelar\x1b[0m',
144
- '\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m',
145
- ''
175
+ `${cyan(S_BAR_START)} ${bold(title || t('UI_SELECT_MULTIPLE'))}`,
176
+ `${cyan(S_BAR)} ${dim2(subtitle || t('UI_AVAILABLE', { count: skills.length }))}`,
177
+ `${cyan(S_BAR)}`
146
178
  ];
147
179
 
148
- // Indicador superior si hay más arriba
149
- if (offset > 0) {
150
- lines.push('\x1b[2m ▲ y más...\x1b[0m');
151
- } else {
152
- lines.push('');
153
- }
154
-
155
180
  const visibleSkills = skills.slice(offset, offset + pageSize);
156
181
  visibleSkills.forEach((skill, index) => {
157
182
  const realIndex = index + offset;
158
183
  const isSelected = selected.has(skill);
159
184
  const isCursor = realIndex === cursor;
160
- const mark = isSelected ? '[x]' : '[ ]';
161
- const pointer = isCursor ? '\x1b[32m❯\x1b[0m' : ' ';
162
- const line = `${mark} ${skill}`;
163
- lines.push(isCursor ? `${pointer} \x1b[1m${line}\x1b[0m` : `${pointer} ${line}`);
185
+
186
+ const check = isSelected
187
+ ? cyan(S_SQUARE_FILL)
188
+ : dim2(S_SQUARE);
189
+
190
+ const pointer = isCursor
191
+ ? cyan(S_POINTER)
192
+ : ' ';
193
+
194
+ // Formatear nombre: owner › name (si tiene /)
195
+ let label = skill;
196
+ if (skill.includes('/')) {
197
+ const [owner, ...nameParts] = skill.split('/');
198
+ const name = nameParts.join('/');
199
+ const ownerPart = isCursor ? bold(owner) : dim2(owner);
200
+ const namePart = isSelected ? cyan(name) : (isCursor ? bold(name) : text(name));
201
+ label = `${ownerPart} ${dim2(S_STEP)} ${namePart}`;
202
+ } else {
203
+ label = isCursor ? bold(skill) : (isSelected ? cyan(skill) : text(skill));
204
+ }
205
+
206
+ lines.push(`${cyan(S_BAR)} ${pointer} ${check} ${label}`);
164
207
  });
165
208
 
166
- // Rellenar hasta pageSize para mantener altura constante (evita parpadeos)
167
209
  for (let i = visibleSkills.length; i < pageSize; i += 1) {
168
- lines.push('');
210
+ lines.push(`${cyan(S_BAR)}`);
169
211
  }
170
212
 
171
- // Indicador inferior si hay más abajo
172
- if (offset + pageSize < skills.length) {
173
- lines.push('\x1b[2m ▼ y más...\x1b[0m');
174
- } else {
175
- lines.push('');
176
- }
213
+ lines.push(`${cyan(S_BAR)}`);
214
+ const status = `(${selected.size}/${skills.length})`;
215
+ const page = t('UI_PAGE', { current: Math.floor(offset / pageSize) + 1, total: Math.ceil(skills.length / pageSize) });
216
+ const controls = t('UI_CONTROLS', { status });
217
+ lines.push(`${cyan(S_BAR_END)} ${dim2(`${page} · ${controls}`)}`);
177
218
 
219
+ const outputContent = lines.join('\n');
178
220
  if (!firstRender) {
179
- readline.moveCursor(output, 0, -renderedLines);
180
- }
181
-
182
- for (let i = 0; i < lines.length; i += 1) {
183
- readline.clearLine(output, 0);
184
- readline.cursorTo(output, 0);
185
- output.write(lines[i]);
186
- if (i < lines.length - 1) output.write('\n');
221
+ output.write('\r' + H_MOVE_UP(renderedLines));
187
222
  }
223
+ output.write(H_CLEAR_DOWN + outputContent);
188
224
 
189
- // Limpiar líneas residuales si el tamaño de renderizado cambió (vía redimensionado de terminal)
190
- if (!firstRender && renderedLines > lines.length) {
191
- for (let i = lines.length; i < renderedLines; i += 1) {
192
- output.write('\n');
193
- readline.clearLine(output, 0);
194
- }
195
- }
196
-
197
- output.write('\n');
198
- renderedLines = lines.length + 1;
225
+ renderedLines = lines.length - 1;
199
226
  firstRender = false;
200
227
  };
201
228
 
202
229
  render();
230
+ const cleanup = () => {
231
+ output.write('\n' + H_SHOW_CURSOR);
232
+ if (typeof input.setRawMode === 'function') input.setRawMode(false);
233
+ input.pause();
234
+ input.removeListener('keypress', onKeypress);
235
+ };
236
+
203
237
  let onKeypress;
204
238
  const outcome = await new Promise((resolve) => {
205
239
  onKeypress = (_, key) => {
240
+ if (!key) return;
241
+ if (key.ctrl && key.name === 'c') {
242
+ cleanup();
243
+ resolve({ cancelled: true });
244
+ return;
245
+ }
206
246
  if (key.name === 'up') {
207
247
  cursor = (cursor - 1 + skills.length) % skills.length;
208
248
  render();
@@ -220,24 +260,29 @@ async function selectSkillsFromList(skills, { title, requireTTYMessage } = {}) {
220
260
  render();
221
261
  return;
222
262
  }
263
+ if (key.name === 'a') {
264
+ if (selected.size === skills.length) {
265
+ selected.clear();
266
+ } else {
267
+ skills.forEach((s) => selected.add(s));
268
+ }
269
+ render();
270
+ return;
271
+ }
223
272
  if (key.name === 'return' || key.name === 'enter') {
273
+ cleanup();
224
274
  resolve({ cancelled: false });
225
275
  return;
226
276
  }
227
- if (key.name === 'escape' || (key.name === 'c' && key.ctrl)) {
277
+ if (key.name === 'escape') {
278
+ cleanup();
228
279
  resolve({ cancelled: true });
229
280
  }
230
281
  };
231
282
  input.on('keypress', onKeypress);
232
283
  });
233
284
 
234
- if (onKeypress) input.off('keypress', onKeypress);
235
- if (typeof input.setRawMode === 'function') input.setRawMode(false);
236
- input.pause();
237
- output.write('\n');
238
-
239
285
  if (outcome.cancelled) {
240
- output.write('\nSelección cancelada.\n');
241
286
  return { selected: [], cancelled: true };
242
287
  }
243
288
 
@@ -279,7 +324,7 @@ function parseRemoteSkillRef(skillRef) {
279
324
  async function fetchRemoteMetadata(skillRef) {
280
325
  const parsed = parseRemoteSkillRef(skillRef);
281
326
  if (!parsed.lookupKeys.length) {
282
- throw new Error('Debes indicar una skill remota válida.');
327
+ throw new Error(t('ERR_REMOTE_INVALID'));
283
328
  }
284
329
 
285
330
  for (const key of parsed.lookupKeys) {
@@ -302,7 +347,7 @@ async function fetchRemoteMetadata(skillRef) {
302
347
  }
303
348
 
304
349
  throw new Error(
305
- `No se pudo obtener metadata remota para "${skillRef}". Usa slug tipo "owner/skill" o URL de skills.sh.`
350
+ t('ERR_REMOTE_METADATA', { ref: skillRef })
306
351
  );
307
352
  }
308
353
 
@@ -313,12 +358,12 @@ async function downloadSkillFromRemote(skillRef, tmpDir) {
313
358
  const sourceUrl = metadata.downloadUrl || metadata.sourceUrl || metadata.repo || metadata.url;
314
359
 
315
360
  if (!sourceUrl) {
316
- throw new Error(`La metadata remota de "${skillRef}" no contiene downloadUrl/sourceUrl/repo/url`);
361
+ throw new Error(t('ERR_REMOTE_NO_URL', { ref: skillRef }));
317
362
  }
318
363
 
319
364
  if (metadata.archiveUrl) {
320
365
  const archiveResponse = await fetch(metadata.archiveUrl);
321
- if (!archiveResponse.ok) throw new Error(`No se pudo descargar archiveUrl (${archiveResponse.status})`);
366
+ if (!archiveResponse.ok) throw new Error(t('ERR_REMOTE_DOWNLOAD', { status: archiveResponse.status }));
322
367
  const archivePath = path.join(tmpDir, `${localName}.tgz`);
323
368
  const buffer = Buffer.from(await archiveResponse.arrayBuffer());
324
369
  await fs.writeFile(archivePath, buffer);
@@ -341,7 +386,7 @@ export async function installRemoteSkill(skillName, { cwd = process.cwd(), force
341
386
  await ensureDir(projectSkillsDir);
342
387
 
343
388
  if (/^https?:\/\/github\.com\//i.test(skillName)) {
344
- throw new Error('Para instalar desde GitHub usa: skillbase install <repo-url> --remote --skill <nombre-skill>.');
389
+ throw new Error(t('ERR_GITHUB_USAGE'));
345
390
  }
346
391
 
347
392
  const tempBase = await fs.mkdtemp(path.join(os.tmpdir(), 'skillbase-'));
@@ -349,7 +394,7 @@ export async function installRemoteSkill(skillName, { cwd = process.cwd(), force
349
394
  const downloaded = await downloadSkillFromRemote(skillName, tempBase);
350
395
  const target = path.join(projectSkillsDir, downloaded.localName);
351
396
  if ((await exists(target)) && !force) {
352
- throw new Error(`La skill "${downloaded.localName}" ya existe en el proyecto. Usa --force para reinstalar.`);
397
+ throw new Error(t('ERR_SKILL_EXISTS', { name: downloaded.localName }));
353
398
  }
354
399
  if (await exists(target)) await fs.rm(target, { recursive: true, force: true });
355
400
  await copyDir(downloaded.path, target);
@@ -372,7 +417,7 @@ export async function installRemoteSkill(skillName, { cwd = process.cwd(), force
372
417
 
373
418
  async function installRemoteFromGitHub(repoUrl, selectedSkill, { cwd = process.cwd(), force = false } = {}) {
374
419
  if (!selectedSkill) {
375
- throw new Error('Falta --skill <nombre>. Ejemplo: skillbase install <repo-url> --remote --skill find-skills');
420
+ throw new Error(t('ERR_SKILL_REQUIRED'));
376
421
  }
377
422
 
378
423
  const projectSkillsDir = getProjectSkillsDir(cwd);
@@ -391,12 +436,12 @@ async function installRemoteFromGitHub(repoUrl, selectedSkill, { cwd = process.c
391
436
  }
392
437
  }
393
438
  if (!sourcePath) {
394
- throw new Error(`No se encontró la skill "${selectedSkill}" en el repo remoto.`);
439
+ throw new Error(t('ERR_SKILL_NOT_FOUND_REMOTE', { name: selectedSkill }));
395
440
  }
396
441
 
397
442
  const target = path.join(projectSkillsDir, selectedSkill);
398
443
  if ((await exists(target)) && !force) {
399
- throw new Error(`La skill "${selectedSkill}" ya existe en el proyecto. Usa --force para reinstalar.`);
444
+ throw new Error(t('ERR_SKILL_EXISTS', { name: selectedSkill }));
400
445
  }
401
446
  if (await exists(target)) await fs.rm(target, { recursive: true, force: true });
402
447
  await copyDir(sourcePath, target);
@@ -427,7 +472,7 @@ export async function installRemoteSkillRef(skillRef, options = {}) {
427
472
  export async function installFromManifest({ cwd = process.cwd(), remote = false, force = false } = {}) {
428
473
  const manifest = await readManifest(cwd);
429
474
  if (!manifest.skills.length) {
430
- throw new Error('No hay skills en skillbase.json. Usa "skillbase add <skill>" o "skillbase install <skill> --remote".');
475
+ throw new Error(t('ERR_MANIFEST_EMPTY'));
431
476
  }
432
477
  for (const skill of manifest.skills) {
433
478
  if (remote || skill.source === 'remote') await installRemoteSkill(skill.name, { cwd, force });
@@ -436,12 +481,13 @@ export async function installFromManifest({ cwd = process.cwd(), remote = false,
436
481
  }
437
482
 
438
483
  export async function removeSkill(skillName, { cwd = process.cwd(), global = false } = {}) {
484
+ const safeName = sanitizeSkillName(skillName);
439
485
  if (global) {
440
- await fs.rm(path.join(getGlobalSkillsDir(), skillName), { recursive: true, force: true });
486
+ await fs.rm(path.join(getGlobalSkillsDir(), safeName), { recursive: true, force: true });
441
487
  return;
442
488
  }
443
489
 
444
- await fs.rm(path.join(getProjectSkillsDir(cwd), skillName), { recursive: true, force: true });
490
+ await fs.rm(path.join(getProjectSkillsDir(cwd), safeName), { recursive: true, force: true });
445
491
  const manifest = await readManifest(cwd);
446
492
  removeSkillFromManifest(manifest, skillName);
447
493
  await writeManifest(manifest, cwd);
@@ -536,8 +582,8 @@ export async function initProject({ cwd = process.cwd(), hard = false } = {}) {
536
582
 
537
583
  const selection = await selectSkillsFromList(suggested, {
538
584
  title: hard
539
- ? 'Init --hard: selecciona skills recomendadas por nombre y tags'
540
- : 'Init: selecciona skills recomendadas por nombre'
585
+ ? t('INIT_HARD_TITLE')
586
+ : t('INIT_TITLE')
541
587
  });
542
588
  if (selection.cancelled) return { technologies, suggested, installed: [], cancelled: true };
543
589
 
package/src/i18n.js ADDED
@@ -0,0 +1,34 @@
1
+ import { getConfig } from './config.js';
2
+ import { en } from './locales/en.js';
3
+ import { es } from './locales/es.js';
4
+
5
+ const locales = { en, es };
6
+
7
+ let currentLang = getConfig().lang || 'en';
8
+
9
+ /**
10
+ * Cambia el idioma actual en memoria
11
+ * @param {string} lang 'en' | 'es'
12
+ */
13
+ export function setLanguage(lang) {
14
+ if (locales[lang]) {
15
+ currentLang = lang;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Traduce una clave al idioma actual
21
+ * @param {string} key Clave del diccionario
22
+ * @param {Object} params Parámetros para reemplazar en la cadena ({key: value})
23
+ * @returns {string} Texto traducido
24
+ */
25
+ export function t(key, params = {}) {
26
+ const dict = locales[currentLang] || locales.en;
27
+ let textStr = dict[key] || locales.en[key] || key;
28
+
29
+ for (const [k, v] of Object.entries(params)) {
30
+ textStr = textStr.replace(new RegExp(`{${k}}`, 'g'), v);
31
+ }
32
+
33
+ return textStr;
34
+ }
@@ -0,0 +1,104 @@
1
+ export const en = {
2
+ // General
3
+ USAGE: 'Usage:',
4
+ COMMANDS: 'Commands:',
5
+ OPTIONS: 'Common Options:',
6
+ SHORTCUTS: 'Shortcuts: l=ls, h=-h, a=add, i=install, rm=remove, c=check, up=update, m=migrate',
7
+ HELP_FOOTER: 'Use {cmd} to see all options and commands.',
8
+ USE_HELP: 'Use {cmd} to see all options and commands.',
9
+ CANCELLED: 'Operation cancelled.',
10
+ COMMANDS_AVAILABLE: 'Available commands:',
11
+ SELECTION_CANCELLED: 'Selection cancelled.',
12
+ UNKNOWN_COMMAND: 'Unknown command: {cmd}. Use skillbase -h',
13
+
14
+ // Commands info
15
+ DESC_INIT: 'Initialize project and detect stack [--hard]',
16
+ DESC_INIT_SHORT: 'Configure project and detect stack',
17
+ DESC_ADD: 'Add one or more skills to the project',
18
+ DESC_ADD_SHORT: 'Add new skill (global/project)',
19
+ DESC_LS: 'List installed skills (local or global with -g)',
20
+ DESC_LS_SHORT: 'List installed skills',
21
+ DESC_INSTALL: 'Install from manifest or remote [-r] [-k <name>]',
22
+ DESC_INSTALL_SHORT: 'Install dependencies from manifest',
23
+ DESC_REMOVE: 'Remove a skill from project or global [-g]',
24
+ DESC_CHECK: 'Check for updates for installed skills',
25
+ DESC_UPDATE: 'Update one or all skills',
26
+ DESC_MIGRATE: 'Migrate (~/.agents) or promote (-p) local skills to global',
27
+ DESC_MIGRATE_SHORT: 'Migrate or promote local skills',
28
+ DESC_LANG: 'Change tool language (en|es)',
29
+ DESC_LANG_SHORT: 'Change CLI language',
30
+
31
+ // Options info
32
+ OPT_REMOTE: 'Operate with remote repositories (GitHub/GitLab)',
33
+ OPT_FORCE: 'Force operation (overwrite files)',
34
+ OPT_SYM: 'Create symbolic links instead of copying (add only)',
35
+ OPT_GLOBAL: 'Operate globally (for ls and remove)',
36
+ OPT_HELP: 'Show this help',
37
+
38
+ // Command-specific messages
39
+ LS_GLOBAL_EMPTY: 'No global skills installed in {dir}',
40
+ LS_GLOBAL_TITLE: 'Global skills ({dir}):',
41
+ LS_PROJECT_EMPTY: 'No local skills installed in {dir}',
42
+ LS_PROJECT_TITLE: 'Project skills ({dir}):',
43
+
44
+ INIT_NO_STACK: 'No stack detected in the project.',
45
+ INIT_ANALYSIS: 'Stack analysis:',
46
+ INIT_RESUMEN: 'Summary:',
47
+ INIT_INSTALLED: 'Installed: {list}',
48
+ INIT_NONE: 'No skills were installed.',
49
+
50
+ ADD_NO_SELECTED: 'No skills selected to install.',
51
+ ADD_RESUMEN: 'Summary:',
52
+ ADD_SUCCESS: 'Skill "{skill}" installed.',
53
+ ADD_REMOTE_SUCCESS: 'Remote skill "{skill}" installed.',
54
+
55
+ REMOVE_GLOBAL_SUCCESS: 'Global skill "{skill}" removed.',
56
+ REMOVE_PROJECT_SUCCESS: 'Project skill "{skill}" removed.',
57
+ REMOVE_NOT_FOUND: 'Skill "{skill}" is not installed.',
58
+
59
+ CHECK_SEARCHING: 'Checking for updates...',
60
+ CHECK_UP_TO_DATE: 'All skills are up to date.',
61
+ CHECK_UPDATES_FOUND: 'Updates available:',
62
+ CHECK_UPDATE_HINT: 'Use {cmd} to update.',
63
+
64
+ UPDATE_START: 'Updating skills...',
65
+ UPDATE_SUCCESS: 'All skills updated.',
66
+ UPDATE_SINGLE_SUCCESS: 'Skill "{skill}" updated.',
67
+
68
+ MIGRATE_TITLE: 'Skills Migration',
69
+ MIGRATE_SOURCE: 'Source: {dir}',
70
+ MIGRATE_FOUND: 'Skills found: {count}',
71
+ MIGRATE_SUCCESS_LIST: 'Skills migrated to {dir}:',
72
+ MIGRATE_SKIPPED_LIST: 'Skipped (already exist):',
73
+ MIGRATE_RESUMEN_TITLE: 'Summary:',
74
+ MIGRATE_RESUMEN_SUCCESS: '{count} skills migrated successfully.',
75
+ MIGRATE_RESUMEN_DEST: 'Destination: {dir}',
76
+ MIGRATE_NONE: 'No new skills were migrated.',
77
+
78
+ LANG_SUCCESS: 'Language changed to {lang} successfully.',
79
+ LANG_INVALID: 'Unsupported language: {lang}. Use "en" or "es".',
80
+
81
+ // Interactive UI
82
+ UI_SELECT_ONE: 'Select a skill',
83
+ UI_SELECT_MULTIPLE: 'Select skills',
84
+ UI_CONTROLS: '↑↓ navigate • [space] sel. {status} • [a] all • [enter] install • [esc] exit',
85
+ UI_SELECTED: 'selected',
86
+ UI_PAGE: 'page {current}/{total}',
87
+ UI_AVAILABLE: '{count} skills available',
88
+ UI_REQUIRED_TTY: 'Interactive selection requires a TTY terminal. Use: skillbase add <skill>.',
89
+
90
+ // Core / Errors
91
+ ERR_GLOBAL_NOT_FOUND: 'Global skill "{name}" does not exist in {dir}',
92
+ ERR_REMOTE_INVALID: 'You must specify a valid remote skill.',
93
+ ERR_REMOTE_METADATA: 'Could not fetch remote metadata for "{ref}". Use slug like "owner/skill" or skills.sh URL.',
94
+ ERR_REMOTE_NO_URL: 'Remote metadata for "{ref}" does not contain downloadUrl/sourceUrl/repo/url',
95
+ ERR_REMOTE_DOWNLOAD: 'Could not download archiveUrl ({status})',
96
+ ERR_GITHUB_USAGE: 'To install from GitHub use: skillbase install <repo-url> --remote --skill <skill-name>.',
97
+ ERR_SKILL_EXISTS: 'Skill "{name}" already exists in the project. Use --force to reinstall.',
98
+ ERR_SKILL_REQUIRED: 'Missing --skill <name>. Example: skillbase install <repo-url> --remote --skill find-skills',
99
+ ERR_SKILL_NOT_FOUND_REMOTE: 'Skill "{name}" not found in remote repo.',
100
+ ERR_MANIFEST_EMPTY: 'No skills in skillbase.json. Use "skillbase add <skill>" or "skillbase install <skill> --remote".',
101
+
102
+ INIT_HARD_TITLE: 'Init --hard: select skills recommended by name and tags',
103
+ INIT_TITLE: 'Init: select skills recommended by name',
104
+ };
@@ -0,0 +1,104 @@
1
+ export const es = {
2
+ // General
3
+ USAGE: 'Uso:',
4
+ COMMANDS: 'Comandos:',
5
+ OPTIONS: 'Opciones comunes:',
6
+ SHORTCUTS: 'Atajos: l=ls, h=-h, a=add, i=install, rm=remove, c=check, up=update, m=migrate',
7
+ HELP_FOOTER: 'Usa {cmd} para ver todas las opciones y comandos.',
8
+ USE_HELP: 'Usa {cmd} para ver todas las opciones y comandos.',
9
+ CANCELLED: 'Operación cancelada.',
10
+ COMMANDS_AVAILABLE: 'Comandos disponibles:',
11
+ SELECTION_CANCELLED: 'Selección cancelada.',
12
+ UNKNOWN_COMMAND: 'Comando desconocido: {cmd}. Usa skillbase -h',
13
+
14
+ // Commands info
15
+ DESC_INIT: 'Inicializa el proyecto y detecta el stack [--hard]',
16
+ DESC_INIT_SHORT: 'Configura proyecto y detecta stack',
17
+ DESC_ADD: 'Añade una o varias skills al proyecto',
18
+ DESC_ADD_SHORT: 'Añadir nueva skill (global/proyecto)',
19
+ DESC_LS: 'Lista las skills instaladas (local o global con -g)',
20
+ DESC_LS_SHORT: 'Listar skills instaladas',
21
+ DESC_INSTALL: 'Instala desde el manifiesto o remoto [-r] [-k <nombre>]',
22
+ DESC_INSTALL_SHORT: 'Instalar dependencias del manifiesto',
23
+ DESC_REMOVE: 'Elimina una skill del proyecto o global [-g]',
24
+ DESC_CHECK: 'Busca actualizaciones de las skills instaladas',
25
+ DESC_UPDATE: 'Actualiza una o todas las skills',
26
+ DESC_MIGRATE: 'Migra (~/.agents) o promueve (-p) skills locales a global',
27
+ DESC_MIGRATE_SHORT: 'Migrar o promover skills locales',
28
+ DESC_LANG: 'Cambia el idioma de la herramienta (en|es)',
29
+ DESC_LANG_SHORT: 'Cambiar idioma de la CLI',
30
+
31
+ // Options info
32
+ OPT_REMOTE: 'Operar con repositorios remotos (GitHub/GitLab)',
33
+ OPT_FORCE: 'Forzar la operación (sobrescribir archivos)',
34
+ OPT_SYM: 'Crear enlaces simbólicos en vez de copiar (solo add)',
35
+ OPT_GLOBAL: 'Operar de forma global (para ls y remove)',
36
+ OPT_HELP: 'Mostrar esta ayuda',
37
+
38
+ // Command-specific messages
39
+ LS_GLOBAL_EMPTY: 'No hay skills globales instaladas en {dir}',
40
+ LS_GLOBAL_TITLE: 'Skills globales ({dir}):',
41
+ LS_PROJECT_EMPTY: 'No hay skills instaladas localmente en {dir}',
42
+ LS_PROJECT_TITLE: 'Skills del proyecto ({dir}):',
43
+
44
+ INIT_NO_STACK: 'No se detectó stack en el proyecto.',
45
+ INIT_ANALYSIS: 'Análisis de stack:',
46
+ INIT_RESUMEN: 'Resumen:',
47
+ INIT_INSTALLED: 'Instaladas: {list}',
48
+ INIT_NONE: 'No se instaló ninguna skill.',
49
+
50
+ ADD_NO_SELECTED: 'No se seleccionaron skills para instalar.',
51
+ ADD_RESUMEN: 'Resumen:',
52
+ ADD_SUCCESS: 'Skill "{skill}" instalada.',
53
+ ADD_REMOTE_SUCCESS: 'Skill remota "{skill}" instalada.',
54
+
55
+ REMOVE_GLOBAL_SUCCESS: 'Skill global "{skill}" eliminada.',
56
+ REMOVE_PROJECT_SUCCESS: 'Skill del proyecto "{skill}" eliminada.',
57
+ REMOVE_NOT_FOUND: 'La skill "{skill}" no está instalada.',
58
+
59
+ CHECK_SEARCHING: 'Buscando actualizaciones...',
60
+ CHECK_UP_TO_DATE: 'Todas las skills están al día.',
61
+ CHECK_UPDATES_FOUND: 'Hay actualizaciones disponibles:',
62
+ CHECK_UPDATE_HINT: 'Usa {cmd} para actualizar.',
63
+
64
+ UPDATE_START: 'Actualizando skills...',
65
+ UPDATE_SUCCESS: 'Todas las skills actualizadas.',
66
+ UPDATE_SINGLE_SUCCESS: 'Skill "{skill}" actualizada.',
67
+
68
+ MIGRATE_TITLE: 'Migración de skills',
69
+ MIGRATE_SOURCE: 'Origen: {dir}',
70
+ MIGRATE_FOUND: 'Skills encontradas: {count}',
71
+ MIGRATE_SUCCESS_LIST: 'Skills migradas a {dir}:',
72
+ MIGRATE_SKIPPED_LIST: 'Omitidas (ya existen):',
73
+ MIGRATE_RESUMEN_TITLE: 'Resumen:',
74
+ MIGRATE_RESUMEN_SUCCESS: '{count} skills migradas con éxito.',
75
+ MIGRATE_RESUMEN_DEST: 'Destino: {dir}',
76
+ MIGRATE_NONE: 'No se migraron nuevas skills.',
77
+
78
+ LANG_SUCCESS: 'Idioma cambiado a {lang} con éxito.',
79
+ LANG_INVALID: 'Idioma no soportado: {lang}. Usa "en" o "es".',
80
+
81
+ // Interfaz Interactiva
82
+ UI_SELECT_ONE: 'Seleccionar una skill',
83
+ UI_SELECT_MULTIPLE: 'Seleccionar skills',
84
+ UI_CONTROLS: '↑↓ navegar • [espacio] sel. {status} • [a] todo • [enter] instalar • [esc] salir',
85
+ UI_SELECTED: 'seleccionadas',
86
+ UI_PAGE: 'pág. {current}/{total}',
87
+ UI_AVAILABLE: '{count} skills disponibles',
88
+ UI_REQUIRED_TTY: 'La selección interactiva requiere una terminal TTY. Usa: skillbase add <skill>.',
89
+
90
+ // Core / Errores
91
+ ERR_GLOBAL_NOT_FOUND: 'La skill global "{name}" no existe en {dir}',
92
+ ERR_REMOTE_INVALID: 'Debes indicar una skill remota válida.',
93
+ ERR_REMOTE_METADATA: 'No se pudo obtener metadata remota para "{ref}". Usa slug tipo "owner/skill" o URL de skills.sh.',
94
+ ERR_REMOTE_NO_URL: 'La metadata remota de "{ref}" no contiene downloadUrl/sourceUrl/repo/url',
95
+ ERR_REMOTE_DOWNLOAD: 'No se pudo descargar archiveUrl ({status})',
96
+ ERR_GITHUB_USAGE: 'Para instalar desde GitHub usa: skillbase install <repo-url> --remote --skill <nombre-skill>.',
97
+ ERR_SKILL_EXISTS: 'La skill "{name}" ya existe en el proyecto. Usa --force para reinstalar.',
98
+ ERR_SKILL_REQUIRED: 'Falta --skill <nombre>. Ejemplo: skillbase install <repo-url> --remote --skill find-skills',
99
+ ERR_SKILL_NOT_FOUND_REMOTE: 'No se encontró la skill "{name}" en el repo remoto.',
100
+ ERR_MANIFEST_EMPTY: 'No hay skills en skillbase.json. Usa "skillbase add <skill>" o "skillbase install <skill> --remote".',
101
+
102
+ INIT_HARD_TITLE: 'Init --hard: selecciona skills recomendadas por nombre y tags',
103
+ INIT_TITLE: 'Init: selecciona skills recomendadas por nombre',
104
+ };