@ariasbruno/skillbase 0.2.0 → 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/cli.js CHANGED
@@ -14,75 +14,118 @@ import {
14
14
  removeSkill,
15
15
  updateSkills
16
16
  } from './core.js';
17
+ import {
18
+ bold,
19
+ dim,
20
+ dim2,
21
+ text,
22
+ grayLogo,
23
+ cyan,
24
+ yellow,
25
+ green,
26
+ red,
27
+ S_DIAMOND,
28
+ S_BULLET,
29
+ S_CHECK,
30
+ S_POINTER,
31
+ S_WARNING,
32
+ S_BAR,
33
+ S_BAR_START,
34
+ S_BAR_END,
35
+ S_STEP
36
+ } from './styles.js';
37
+ import { createRequire } from 'node:module';
38
+ import { t, setLanguage } from './i18n.js';
39
+ import { getConfig, saveConfig } from './config.js';
40
+
41
+ const require = createRequire(import.meta.url);
42
+ const pkg = require('../package.json');
43
+
44
+ function printLogo() {
45
+ const LOGO_LINES = [
46
+ ' ███████╗██╗ ██╗██╗██╗ ██╗ ██████╗ █████╗ ███████╗███████╗',
47
+ ' ██╔════╝██║ ██╔╝██║██║ ██║ ██╔══██╗██╔══██╗██╔════╝██╔════╝',
48
+ ' ███████╗█████╔╝ ██║██║ ██║ ██████╔╝███████║███████╗█████╗ ',
49
+ ' ╚════██║██╔═██╗ ██║██║ ██║ ██╔══██╗██╔══██║╚════██║██╔══╝ ',
50
+ ' ███████║██║ ██╗██║███████╗███████╗██████╔╝██║ ██║███████║███████╗',
51
+ ' ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝',
52
+ ];
53
+
54
+ console.log();
55
+ LOGO_LINES.forEach((line, i) => {
56
+ console.log(grayLogo(line, i));
57
+ });
58
+ console.log(` ${dim2(pkg.description)} ${dim2(`v${pkg.version}`)}`);
59
+ console.log();
60
+ }
17
61
 
62
+ /**
63
+ * Muestra la ayuda principal de la CLI con el estilo Clack.
64
+ */
18
65
  function printHelp() {
19
- console.log(`skillbase - Gestor de skills locales/remotas
20
-
21
- Uso:
22
- skillbase -h
23
- skillbase h
24
- skillbase ls [--project]
25
- skillbase l [-p]
26
- skillbase init [--hard]
27
- skillbase add <skill> [--sym]
28
- skillbase add
29
- skillbase a <skill> [-s]
30
- skillbase install
31
- skillbase i
32
- skillbase install <skill|repo-url> --remote [--skill <name>] [--force]
33
- skillbase remove <skill> [--g]
34
- skillbase rm <skill> [--g]
35
- skillbase check [--remote]
36
- skillbase c [-r]
37
- skillbase update [<skill>] [--remote] [--force]
38
- skillbase up [<skill>] [-r] [-f]
39
- skillbase migrate [--project] [--force]
40
- skillbase m [--project] [-f]
41
-
42
- Descripción breve de comandos:
43
- ls Lista skills instaladas globalmente (~/.skillbase/skills) o del proyecto con --project.
44
- init Sugiere skills globales según tecnologías del proyecto.
45
- add Instala una skill global (o abre selector interactivo sin argumentos).
46
- install Instala desde skillbase.json o una skill remota con --remote.
47
- remove Elimina una skill del proyecto o global con --g.
48
- check Revisa si hay versiones más nuevas disponibles.
49
- update Actualiza skills del manifiesto (todas o una puntual).
50
- migrate Migra skills desde .agents hacia ~/skillbase/skills.
51
-
52
- Aliases:
53
- l=ls, h=-h, a=add, i=install, rm=remove, c=check, up=update, m=migrate
54
-
55
- Short flags:
56
- -s=--sym, -r=--remote, -f=--force, -k=--skill, -p=--project
57
-
58
- Notas:
59
- - Carpeta global por defecto: ~/.skillbase/skills
60
- - Puedes cambiarla con SKILLBASE_HOME
61
- - Las skills de proyecto viven en .agents/skills
62
- - El manifiesto del proyecto es skillbase.json
63
- `);
66
+ printLogo();
67
+ console.log(`${cyan(S_BAR_START)} ${bold(t('USAGE'))}`);
68
+ console.log(`${cyan(S_BAR)} ${green('skillbase')} ${text('<command> [args] [options]')}`);
69
+ console.log(`${cyan(S_BAR)}`);
70
+
71
+ console.log(`${cyan(S_BAR)} ${bold(t('COMMANDS'))}`);
72
+ const commands = [
73
+ ['ls', `[args]`, t('DESC_LS')],
74
+ ['init', ``, t('DESC_INIT')],
75
+ ['add', `<skill>`, t('DESC_ADD')],
76
+ ['install', `[url]`, t('DESC_INSTALL')],
77
+ ['remove', `<skill>`, t('DESC_REMOVE')],
78
+ ['check', ``, t('DESC_CHECK')],
79
+ ['update', `[skill]`, t('DESC_UPDATE')],
80
+ ['migrate', ``, t('DESC_MIGRATE')],
81
+ ['lang', `<lang>`, t('DESC_LANG')],
82
+ ];
83
+
84
+ commands.forEach(([cmd, args, desc]) => {
85
+ const fullCmd = `${bold(cmd.padEnd(8))} ${dim2(args.padEnd(8))}`;
86
+ console.log(`${cyan(S_BAR)} ${fullCmd} ${text(desc)}`);
87
+ });
88
+
89
+ console.log(`${cyan(S_BAR)}`);
90
+ console.log(`${cyan(S_BAR)} ${bold(t('OPTIONS'))}`);
91
+ const options = [
92
+ ['-r, --remote ', t('OPT_REMOTE')],
93
+ ['-f, --force ', t('OPT_FORCE')],
94
+ ['-s, --sym ', t('OPT_SYM')],
95
+ ['-g, --global ', t('OPT_GLOBAL')],
96
+ ['-h, --help ', t('OPT_HELP')],
97
+ ];
98
+
99
+ options.forEach(([opt, desc]) => {
100
+ console.log(`${cyan(S_BAR)} ${dim2(opt.padEnd(14))} ${dim2(desc)}`);
101
+ });
102
+
103
+ console.log(`${cyan(S_BAR)}`);
104
+ console.log(`${cyan(S_BAR_END)} ${dim2(t('SHORTCUTS'))}\n`);
64
105
  }
65
106
 
107
+ /**
108
+ * Muestra una lista resumida de comandos cuando no se indica ninguno.
109
+ */
66
110
  function printCommandList() {
67
- console.log(`Comandos disponibles:
68
- - skillbase ls [--project]
69
- - skillbase l [-p]
70
- - skillbase init [--hard]
71
- - skillbase add
72
- - skillbase add <skill> [--sym]
73
- - skillbase a <skill> [-s]
74
- - skillbase install | skillbase i
75
- - skillbase install <skill|repo-url> --remote [--skill <name>] [--force]
76
- - skillbase remove <skill> [--g]
77
- - skillbase rm <skill> [--g]
78
- - skillbase check [--remote]
79
- - skillbase c [-r]
80
- - skillbase update [<skill>] [--remote] [--force]
81
- - skillbase up [<skill>] [-r] [-f]
82
- - skillbase migrate [--project] [--force]
83
- - skillbase m [--project] [-f]
84
-
85
- Usa "skillbase -h" para ver detalles.`);
111
+ printLogo();
112
+ console.log(`${cyan(S_BAR_START)} ${bold(t('COMMANDS_AVAILABLE'))}`);
113
+
114
+ const quickList = [
115
+ ['ls', t('DESC_LS_SHORT')],
116
+ ['init', t('DESC_INIT_SHORT')],
117
+ ['add', t('DESC_ADD_SHORT')],
118
+ ['install', t('DESC_INSTALL_SHORT')],
119
+ ['migrate', t('DESC_MIGRATE_SHORT')],
120
+ ['lang', t('DESC_LANG_SHORT')],
121
+ ];
122
+
123
+ quickList.forEach(([cmd, desc]) => {
124
+ console.log(`${cyan(S_BAR)} ${bold(cmd.padEnd(12))} ${dim2(desc)}`);
125
+ });
126
+
127
+ console.log(`${cyan(S_BAR)}`);
128
+ console.log(`${cyan(S_BAR_END)} ${dim2(t('USE_HELP', { cmd: yellow('skillbase --help') }))}\n`);
86
129
  }
87
130
 
88
131
  function hasFlag(args, ...flags) {
@@ -96,6 +139,9 @@ function getFlagValue(args, ...flags) {
96
139
  return null;
97
140
  }
98
141
 
142
+ /**
143
+ * Punto de entrada principal de la CLI.
144
+ */
99
145
  export async function runCLI(argv) {
100
146
  const args = argv.slice(2);
101
147
  const [rawCommand, maybeSkill] = args;
@@ -116,73 +162,89 @@ export async function runCLI(argv) {
116
162
  return;
117
163
  }
118
164
 
119
- if (command === '-h' || command === '--help') {
165
+ if (command === '-h' || command === '--help' || hasFlag(args, '-h', '--help')) {
120
166
  printHelp();
121
167
  return;
122
168
  }
123
169
 
124
170
  switch (command) {
125
171
  case 'ls': {
126
- const showProject = hasFlag(args, '--project', '-p');
127
- if (showProject) {
128
- const skills = await listProjectSkills();
172
+ const showGlobal = hasFlag(args, '--global', '-g');
173
+ if (showGlobal) {
174
+ const skills = await listGlobalSkills();
129
175
  if (!skills.length) {
130
- console.log(`No hay skills instaladas localmente en ${getProjectSkillsDir()}`);
176
+ console.log(`${yellow(S_WARNING)} ${t('LS_GLOBAL_EMPTY', { dir: dim2(getGlobalSkillsDir()) })}`);
131
177
  } else {
132
- console.log(`Skills del proyecto (${getProjectSkillsDir()}):`);
133
- skills.forEach((skill) => console.log(`- ${skill}`));
178
+ console.log(`${cyan(S_BAR_START)} ${bold(t('LS_GLOBAL_TITLE', { dir: dim2(getGlobalSkillsDir()) }))}`);
179
+ skills.forEach((skill) => console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${text(skill)}`));
180
+ console.log(`${cyan(S_BAR_END)}\n`);
134
181
  }
135
182
  } else {
136
- const skills = await listGlobalSkills();
183
+ const skills = await listProjectSkills();
137
184
  if (!skills.length) {
138
- console.log(`No hay skills globales instaladas en ${getGlobalSkillsDir()}`);
185
+ console.log(`${yellow(S_WARNING)} ${t('LS_PROJECT_EMPTY', { dir: dim2(getProjectSkillsDir()) })}`);
139
186
  } else {
140
- console.log(`Skills globales (${getGlobalSkillsDir()}):`);
141
- skills.forEach((skill) => console.log(`- ${skill}`));
187
+ console.log(`${cyan(S_BAR_START)} ${bold(t('LS_PROJECT_TITLE', { dir: dim2(getProjectSkillsDir()) }))}`);
188
+ skills.forEach((skill) => console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${text(skill)}`));
189
+ console.log(`${cyan(S_BAR_END)}\n`);
142
190
  }
143
191
  }
144
192
  return;
145
193
  }
146
194
 
147
195
  case 'init': {
196
+ printLogo();
148
197
  const result = await initProject({ hard: hasFlag(args, '--hard') });
149
- if (!result.technologies.length) {
150
- console.log('No se detectaron tecnologías en el proyecto.');
151
- return;
152
- }
153
- console.log(`Tecnologías detectadas: ${result.technologies.join(', ')}`);
154
- if (!result.suggested.length) {
155
- console.log('No se encontraron skills compatibles en ~/.skillbase/skills.');
198
+
199
+ if (result.cancelled) {
200
+ console.log(`${cyan(S_BAR_START)} ${dim2(t('CANCELLED'))}`);
201
+ console.log(`${cyan(S_BAR_END)}`);
156
202
  return;
157
203
  }
158
- if (result.cancelled) {
159
- console.log('Selección cancelada.');
204
+
205
+ if (!result.technologies.length) {
206
+ console.log(`${yellow(S_WARNING)} ${t('INIT_NO_STACK')}`);
160
207
  return;
161
208
  }
209
+
210
+ console.log(`${cyan(S_BAR_START)} ${bold(t('INIT_ANALYSIS'))}`);
211
+ result.technologies.forEach((tech) => {
212
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${text(tech)}`);
213
+ });
214
+ console.log(`${cyan(S_BAR)}`);
215
+
162
216
  if (result.installed.length) {
163
- console.log(`Skills instaladas: ${result.installed.join(', ')}`);
217
+ console.log(`${cyan(S_BAR_START)} ${bold(t('INIT_RESUMEN'))}`);
218
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('INIT_INSTALLED', { list: bold(result.installed.join(', ')) })}`);
219
+ console.log(`${cyan(S_BAR_END)}\n`);
164
220
  } else {
165
- console.log('No se seleccionaron skills para instalar.');
221
+ console.log(`${cyan(S_BAR_END)} ${dim2(t('INIT_NONE'))}\n`);
166
222
  }
167
223
  return;
168
224
  }
169
225
 
170
226
  case 'add': {
171
227
  if (!maybeSkill) {
228
+ printLogo();
172
229
  const result = await addSkillsInteractive({ sym: hasFlag(args, '--sym', '-s') });
173
230
  if (result.cancelled) {
174
- console.log('Selección cancelada.');
231
+ console.log(`${cyan(S_BAR_START)} ${dim2(t('SELECTION_CANCELLED'))}`);
232
+ console.log(`${cyan(S_BAR_END)}`);
175
233
  return;
176
234
  }
177
235
  if (!result.selected.length) {
178
- console.log('No se seleccionaron skills para instalar.');
236
+ console.log(`${cyan(S_BAR_END)} ${dim2(t('ADD_NO_SELECTED'))}\n`);
179
237
  return;
180
238
  }
181
- console.log(`Skills instaladas: ${result.selected.join(', ')}`);
239
+ console.log(`${cyan(S_BAR_START)} ${bold(t('ADD_RESUMEN'))}`);
240
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('INIT_INSTALLED', { list: bold(result.selected.join(', ')) })}`);
241
+ console.log(`${cyan(S_BAR_END)}\n`);
182
242
  return;
183
243
  }
184
244
  await addSkill(maybeSkill, { sym: hasFlag(args, '--sym', '-s') });
185
- console.log(`Skill "${maybeSkill}" instalada en el proyecto.`);
245
+ console.log(`${cyan(S_BAR_START)} ${bold(t('ADD_RESUMEN'))}`);
246
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('ADD_SUCCESS', { skill: bold(maybeSkill) })}`);
247
+ console.log(`${cyan(S_BAR_END)}\n`);
186
248
  return;
187
249
  }
188
250
 
@@ -192,77 +254,117 @@ export async function runCLI(argv) {
192
254
  force: hasFlag(args, '--force', '-f'),
193
255
  skill: getFlagValue(args, '--skill', '-k')
194
256
  });
195
- console.log(`Skill remota "${maybeSkill}" instalada.`);
257
+ console.log(`${cyan(S_BAR_START)} ${bold(t('INIT_RESUMEN'))}`);
258
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('ADD_REMOTE_SUCCESS', { skill: bold(maybeSkill) })}`);
259
+ console.log(`${cyan(S_BAR_END)}\n`);
196
260
  return;
197
261
  }
198
262
  if (maybeSkill && !hasFlag(args, '--remote', '-r')) {
199
- throw new Error('Para instalar una skill puntual usa "skillbase add <skill>" (global) o "skillbase install <skill|repo-url> --remote".');
263
+ throw new Error(t('INSTALL_ERROR_SINGLE'));
200
264
  }
201
265
  await installFromManifest({ remote: hasFlag(args, '--remote', '-r'), force: hasFlag(args, '--force', '-f') });
202
266
  const manifest = await readManifest();
203
- console.log(`Instaladas ${manifest.skills.length} skills desde skillbase.json.`);
267
+ console.log(`${cyan(S_BAR_START)} ${bold(t('INIT_RESUMEN'))}`);
268
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('INSTALL_MANIFEST_SUCCESS', { count: bold(manifest.skills.length) })}`);
269
+ console.log(`${cyan(S_BAR_END)}\n`);
204
270
  return;
205
271
  }
206
272
 
207
273
  case 'remove': {
208
- if (!maybeSkill) throw new Error('Debes indicar una skill: skillbase remove <skill>');
209
- await removeSkill(maybeSkill, { global: hasFlag(args, '--g') });
210
- if (hasFlag(args, '--g')) {
211
- console.log(`Skill global "${maybeSkill}" eliminada.`);
274
+ if (!maybeSkill) throw new Error(t('REMOVE_REQUIRED'));
275
+ const isGlobal = hasFlag(args, '-g', '--global');
276
+ await removeSkill(maybeSkill, { global: isGlobal });
277
+ console.log(`${cyan(S_BAR_START)} ${bold(t('INIT_RESUMEN'))}`);
278
+ if (isGlobal) {
279
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('REMOVE_GLOBAL_SUCCESS', { skill: bold(maybeSkill) })}`);
212
280
  } else {
213
- console.log(`Skill "${maybeSkill}" eliminada del proyecto.`);
281
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('REMOVE_PROJECT_SUCCESS', { skill: bold(maybeSkill) })}`);
214
282
  }
283
+ console.log(`${cyan(S_BAR_END)}\n`);
215
284
  return;
216
285
  }
217
286
 
218
287
  case 'check': {
288
+ console.log(`${cyan(S_BAR_START)} ${dim2(t('CHECK_SEARCHING'))}`);
219
289
  const updates = await checkUpdates({ remoteOnly: hasFlag(args, '--remote', '-r') });
220
290
  if (!updates.length) {
221
- console.log('No hay actualizaciones disponibles.');
291
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('CHECK_UP_TO_DATE')}`);
292
+ console.log(`${cyan(S_BAR_END)}\n`);
222
293
  return;
223
294
  }
224
- console.log('Actualizaciones disponibles:');
295
+ console.log(`${cyan(S_BAR)} ${bold(t('CHECK_UPDATES_FOUND'))}`);
225
296
  updates.forEach((item) => {
226
- console.log(`- ${item.name}: ${item.current ?? 'desconocida'} -> ${item.latest} (${item.source})`);
297
+ console.log(`${cyan(S_BAR)} ${yellow(S_POINTER)} ${item.name}: ${dim2(item.current ?? '?')} ${dim2(S_STEP)} ${green(item.latest)} ${dim2(`(${item.source})`)}`);
227
298
  });
299
+ console.log(`${cyan(S_BAR)}`);
300
+ console.log(`${cyan(S_BAR_END)} ${dim2(t('CHECK_UPDATE_HINT', { cmd: yellow('skillbase update') }))}\n`);
228
301
  return;
229
302
  }
230
303
 
231
304
  case 'update': {
232
305
  const skill = maybeSkill && !maybeSkill.startsWith('-') ? maybeSkill : null;
306
+ console.log(`${cyan(S_BAR_START)} ${dim2(t('UPDATE_START'))}`);
233
307
  await updateSkills({
234
308
  skillName: skill,
235
309
  remoteOnly: hasFlag(args, '--remote', '-remote', '-r'),
236
310
  force: hasFlag(args, '--force', '-f')
237
311
  });
312
+ console.log(`${cyan(S_BAR)} ${bold(t('INIT_RESUMEN'))}`);
238
313
  if (skill) {
239
- console.log(`Skill "${skill}" actualizada.`);
314
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('UPDATE_SINGLE_SUCCESS', { skill: bold(skill) })}`);
240
315
  } else {
241
- console.log('Skills actualizadas.');
316
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('UPDATE_SUCCESS')}`);
242
317
  }
318
+ console.log(`${cyan(S_BAR_END)}\n`);
243
319
  return;
244
320
  }
245
321
 
246
322
  case 'migrate': {
247
- const fromProject = hasFlag(args, '--project', '-p');
323
+ const fromProject = hasFlag(args, '--promote', '-p');
248
324
  const result = await migrateAgentsSkillsToSkillbase({
249
325
  force: hasFlag(args, '--force', '-f'),
250
326
  fromProject
251
327
  });
252
- console.log(`Origen de migración: ${result.sourceRoot}/skills`);
253
- console.log(`Skills encontradas: ${result.totalFound}`);
328
+ console.log(`${cyan(S_BAR_START)} ${bold(t('MIGRATE_TITLE'))}`);
329
+ console.log(`${cyan(S_BAR)} ${dim2(S_POINTER)} ${t('MIGRATE_SOURCE', { dir: dim2(`${result.sourceRoot}/skills`) })}`);
330
+ console.log(`${cyan(S_BAR)} ${dim2(S_POINTER)} ${t('MIGRATE_FOUND', { count: bold(result.totalFound) })}`);
331
+
254
332
  if (result.migrated.length) {
255
- console.log(`Migradas a ${getGlobalSkillsDir()}:`);
256
- result.migrated.forEach((skill) => console.log(`- ${skill}`));
333
+ console.log(`${cyan(S_BAR)}`);
334
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('MIGRATE_SUCCESS_LIST', { dir: dim2(getGlobalSkillsDir()) })}`);
335
+ result.migrated.forEach((skill) => console.log(`${cyan(S_BAR)} ${dim2('·')} ${text(skill)}`));
257
336
  }
258
337
  if (result.skipped.length) {
259
- console.log('Omitidas (ya existen globalmente, usa --force para sobrescribir):');
260
- result.skipped.forEach((skill) => console.log(`- ${skill}`));
338
+ console.log(`${cyan(S_BAR)}`);
339
+ console.log(`${cyan(S_BAR)} ${yellow(S_POINTER)} ${t('MIGRATE_SKIPPED_LIST')}`);
340
+ result.skipped.forEach((skill) => console.log(`${cyan(S_BAR)} ${dim2('·')} ${text(skill)}`));
341
+ }
342
+
343
+ if (result.migrated.length) {
344
+ console.log(`${cyan(S_BAR)}`);
345
+ console.log(`${cyan(S_BAR)} ${bold(t('INIT_RESUMEN'))}`);
346
+ console.log(`${cyan(S_BAR)} ${green(S_CHECK)} ${t('MIGRATE_RESUMEN_SUCCESS', { count: bold(result.migrated.length) })}`);
347
+ console.log(`${cyan(S_BAR)} ${dim2(S_POINTER)} ${t('MIGRATE_RESUMEN_DEST', { dir: bold(getGlobalSkillsDir()) })}`);
348
+ console.log(`${cyan(S_BAR_END)}\n`);
349
+ } else {
350
+ console.log(`${cyan(S_BAR_END)} ${dim2(t('MIGRATE_NONE'))}\n`);
351
+ }
352
+ return;
353
+ }
354
+
355
+ case 'lang': {
356
+ if (!maybeSkill || !['en', 'es'].includes(maybeSkill)) {
357
+ throw new Error(t('LANG_INVALID', { lang: maybeSkill || '??' }));
261
358
  }
359
+ const config = await getConfig();
360
+ config.lang = maybeSkill;
361
+ saveConfig(config);
362
+ console.log(`${cyan(S_BAR_START)} ${bold(t('LANG_SUCCESS', { lang: maybeSkill === 'es' ? 'Español' : 'English' }))}`);
363
+ console.log(`${cyan(S_BAR_END)}\n`);
262
364
  return;
263
365
  }
264
366
 
265
367
  default:
266
- throw new Error(`Comando desconocido: ${command}. Usa skillbase -h`);
368
+ throw new Error(t('UNKNOWN_COMMAND', { cmd: command }));
267
369
  }
268
370
  }
package/src/config.js CHANGED
@@ -1,3 +1,5 @@
1
+ import fs from 'node:fs';
2
+ import fsp from 'node:fs/promises';
1
3
  import os from 'node:os';
2
4
  import path from 'node:path';
3
5
 
@@ -5,6 +7,16 @@ export const MANIFEST_FILE = 'skillbase.json';
5
7
  export const PROJECT_AGENTS_DIR = '.agents';
6
8
  export const PROJECT_SKILLS_DIR = 'skills';
7
9
  export const DEFAULT_GLOBAL_ROOTNAME = '.skillbase';
10
+ export const CONFIG_FILE = 'config.json';
11
+
12
+ export async function exists(target) {
13
+ try {
14
+ await fsp.access(target);
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
8
20
 
9
21
  export function getGlobalRootDir() {
10
22
  const fromEnv = process.env.SKILLBASE_HOME;
@@ -16,6 +28,31 @@ export function getGlobalSkillsDir() {
16
28
  return path.join(getGlobalRootDir(), PROJECT_SKILLS_DIR);
17
29
  }
18
30
 
31
+ export function getGlobalConfigPath() {
32
+ return path.join(getGlobalRootDir(), CONFIG_FILE);
33
+ }
34
+
35
+ export function getConfig() {
36
+ const configPath = getGlobalConfigPath();
37
+ try {
38
+ if (fs.existsSync(configPath)) {
39
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
40
+ }
41
+ } catch {
42
+ // Omitir errores de lectura
43
+ }
44
+ return { lang: 'en' };
45
+ }
46
+
47
+ export function saveConfig(config) {
48
+ const configPath = getGlobalConfigPath();
49
+ const rootDir = getGlobalRootDir();
50
+ if (!fs.existsSync(rootDir)) {
51
+ fs.mkdirSync(rootDir, { recursive: true });
52
+ }
53
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
54
+ }
55
+
19
56
  export function getProjectRoot(cwd = process.cwd()) {
20
57
  return cwd;
21
58
  }