@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/AGENTS.md +11 -5
- package/README.md +68 -60
- package/README_es.md +132 -0
- package/package.json +12 -3
- package/src/cli.js +224 -107
- package/src/config.js +37 -0
- package/src/core.js +135 -89
- package/src/i18n.js +34 -0
- package/src/locales/en.js +104 -0
- package/src/locales/es.js +104 -0
- package/src/manifest.js +8 -12
- package/src/recommendations.js +7 -10
- package/src/styles.js +59 -0
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 {
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
100
|
+
const safeName = sanitizeSkillName(skillName);
|
|
101
|
+
const globalPath = path.join(getGlobalSkillsDir(), safeName);
|
|
65
102
|
if (!(await exists(globalPath))) {
|
|
66
|
-
throw new Error(
|
|
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,
|
|
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: '
|
|
97
|
-
requireTTYMessage: '
|
|
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(`\
|
|
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 || '
|
|
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 =
|
|
123
|
-
return Math.max(5, Math.min(
|
|
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
|
-
|
|
134
|
-
if (cursor
|
|
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
|
-
'
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
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('
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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(
|
|
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(
|
|
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('
|
|
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(),
|
|
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),
|
|
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
|
-
? '
|
|
540
|
-
: '
|
|
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
|
+
};
|