@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/AGENTS.md +7 -6
- package/README.md +67 -61
- package/README_es.md +132 -0
- package/package.json +12 -3
- package/src/cli.js +213 -111
- package/src/config.js +37 -0
- package/src/core.js +128 -88
- 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() {
|
|
@@ -68,14 +97,15 @@ function compareVersion(a, b) {
|
|
|
68
97
|
}
|
|
69
98
|
|
|
70
99
|
export async function addSkill(skillName, { sym = false, cwd = process.cwd() } = {}) {
|
|
71
|
-
const
|
|
100
|
+
const safeName = sanitizeSkillName(skillName);
|
|
101
|
+
const globalPath = path.join(getGlobalSkillsDir(), safeName);
|
|
72
102
|
if (!(await exists(globalPath))) {
|
|
73
|
-
throw new Error(
|
|
103
|
+
throw new Error(t('ERR_GLOBAL_NOT_FOUND', { name: skillName, dir: getGlobalSkillsDir() }));
|
|
74
104
|
}
|
|
75
105
|
|
|
76
106
|
const projectSkillsDir = getProjectSkillsDir(cwd);
|
|
77
107
|
await ensureDir(projectSkillsDir);
|
|
78
|
-
const target = path.join(projectSkillsDir,
|
|
108
|
+
const target = path.join(projectSkillsDir, safeName);
|
|
79
109
|
|
|
80
110
|
if (await exists(target)) {
|
|
81
111
|
await fs.rm(target, { recursive: true, force: true });
|
|
@@ -100,21 +130,21 @@ export async function addSkillsInteractive({ cwd = process.cwd(), sym = false }
|
|
|
100
130
|
const skills = await listGlobalSkills();
|
|
101
131
|
if (!skills.length) return { selected: [], cancelled: false };
|
|
102
132
|
const selection = await selectSkillsFromList(skills, {
|
|
103
|
-
title: '
|
|
104
|
-
requireTTYMessage: '
|
|
133
|
+
title: t('UI_SELECT_MULTIPLE'),
|
|
134
|
+
requireTTYMessage: t('UI_REQUIRED_TTY')
|
|
105
135
|
});
|
|
106
136
|
if (selection.cancelled) return { selected: [], cancelled: true };
|
|
107
137
|
const selectedSkills = selection.selected;
|
|
108
138
|
for (const skill of selectedSkills) {
|
|
109
139
|
await addSkill(skill, { cwd, sym });
|
|
110
140
|
}
|
|
111
|
-
output.write(`\
|
|
141
|
+
output.write(`\n${t('INIT_INSTALLED', { list: selectedSkills.join(', ') || t('INIT_NONE') })}\n`);
|
|
112
142
|
return { selected: selectedSkills, cancelled: false };
|
|
113
143
|
}
|
|
114
144
|
|
|
115
|
-
async function selectSkillsFromList(skills, { title, requireTTYMessage } = {}) {
|
|
145
|
+
async function selectSkillsFromList(skills, { title, subtitle, requireTTYMessage } = {}) {
|
|
116
146
|
if (!input.isTTY || !output.isTTY) {
|
|
117
|
-
throw new Error(requireTTYMessage || '
|
|
147
|
+
throw new Error(requireTTYMessage || t('UI_REQUIRED_TTY'));
|
|
118
148
|
}
|
|
119
149
|
|
|
120
150
|
const selected = new Set();
|
|
@@ -123,93 +153,96 @@ async function selectSkillsFromList(skills, { title, requireTTYMessage } = {}) {
|
|
|
123
153
|
let renderedLines = 0;
|
|
124
154
|
let firstRender = true;
|
|
125
155
|
|
|
126
|
-
// Tamaño de página adaptativo (mínimo 5, máximo 15 por defecto)
|
|
127
156
|
const getPageSize = () => {
|
|
128
157
|
const terminalRows = output.rows || 24;
|
|
129
|
-
const reservedRows =
|
|
130
|
-
return Math.max(5, Math.min(
|
|
158
|
+
const reservedRows = 10;
|
|
159
|
+
return Math.max(5, Math.min(12, terminalRows - reservedRows));
|
|
131
160
|
};
|
|
132
161
|
|
|
133
162
|
readline.emitKeypressEvents(input);
|
|
134
163
|
input.resume();
|
|
135
164
|
if (typeof input.setRawMode === 'function') input.setRawMode(true);
|
|
136
165
|
|
|
166
|
+
output.write(H_HIDE_CURSOR);
|
|
167
|
+
|
|
137
168
|
const render = () => {
|
|
138
169
|
const pageSize = getPageSize();
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (cursor
|
|
142
|
-
offset = cursor;
|
|
143
|
-
} else if (cursor >= offset + pageSize) {
|
|
144
|
-
offset = cursor - pageSize + 1;
|
|
145
|
-
}
|
|
170
|
+
|
|
171
|
+
if (cursor < offset) offset = cursor;
|
|
172
|
+
else if (cursor >= offset + pageSize) offset = cursor - pageSize + 1;
|
|
146
173
|
|
|
147
174
|
const lines = [
|
|
148
|
-
'
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
'\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m',
|
|
152
|
-
''
|
|
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)}`
|
|
153
178
|
];
|
|
154
179
|
|
|
155
|
-
// Indicador superior si hay más arriba
|
|
156
|
-
if (offset > 0) {
|
|
157
|
-
lines.push('\x1b[2m ▲ y más...\x1b[0m');
|
|
158
|
-
} else {
|
|
159
|
-
lines.push('');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
180
|
const visibleSkills = skills.slice(offset, offset + pageSize);
|
|
163
181
|
visibleSkills.forEach((skill, index) => {
|
|
164
182
|
const realIndex = index + offset;
|
|
165
183
|
const isSelected = selected.has(skill);
|
|
166
184
|
const isCursor = realIndex === cursor;
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
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}`);
|
|
171
207
|
});
|
|
172
208
|
|
|
173
|
-
// Rellenar hasta pageSize para mantener altura constante (evita parpadeos)
|
|
174
209
|
for (let i = visibleSkills.length; i < pageSize; i += 1) {
|
|
175
|
-
lines.push(
|
|
210
|
+
lines.push(`${cyan(S_BAR)}`);
|
|
176
211
|
}
|
|
177
212
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
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}`)}`);
|
|
184
218
|
|
|
219
|
+
const outputContent = lines.join('\n');
|
|
185
220
|
if (!firstRender) {
|
|
186
|
-
|
|
221
|
+
output.write('\r' + H_MOVE_UP(renderedLines));
|
|
187
222
|
}
|
|
223
|
+
output.write(H_CLEAR_DOWN + outputContent);
|
|
188
224
|
|
|
189
|
-
|
|
190
|
-
readline.clearLine(output, 0);
|
|
191
|
-
readline.cursorTo(output, 0);
|
|
192
|
-
output.write(lines[i]);
|
|
193
|
-
if (i < lines.length - 1) output.write('\n');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Limpiar líneas residuales si el tamaño de renderizado cambió (vía redimensionado de terminal)
|
|
197
|
-
if (!firstRender && renderedLines > lines.length) {
|
|
198
|
-
for (let i = lines.length; i < renderedLines; i += 1) {
|
|
199
|
-
output.write('\n');
|
|
200
|
-
readline.clearLine(output, 0);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
output.write('\n');
|
|
205
|
-
renderedLines = lines.length + 1;
|
|
225
|
+
renderedLines = lines.length - 1;
|
|
206
226
|
firstRender = false;
|
|
207
227
|
};
|
|
208
228
|
|
|
209
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
|
+
|
|
210
237
|
let onKeypress;
|
|
211
238
|
const outcome = await new Promise((resolve) => {
|
|
212
239
|
onKeypress = (_, key) => {
|
|
240
|
+
if (!key) return;
|
|
241
|
+
if (key.ctrl && key.name === 'c') {
|
|
242
|
+
cleanup();
|
|
243
|
+
resolve({ cancelled: true });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
213
246
|
if (key.name === 'up') {
|
|
214
247
|
cursor = (cursor - 1 + skills.length) % skills.length;
|
|
215
248
|
render();
|
|
@@ -227,22 +260,28 @@ async function selectSkillsFromList(skills, { title, requireTTYMessage } = {}) {
|
|
|
227
260
|
render();
|
|
228
261
|
return;
|
|
229
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
|
+
}
|
|
230
272
|
if (key.name === 'return' || key.name === 'enter') {
|
|
273
|
+
cleanup();
|
|
231
274
|
resolve({ cancelled: false });
|
|
232
275
|
return;
|
|
233
276
|
}
|
|
234
|
-
if (key.name === 'escape'
|
|
277
|
+
if (key.name === 'escape') {
|
|
278
|
+
cleanup();
|
|
235
279
|
resolve({ cancelled: true });
|
|
236
280
|
}
|
|
237
281
|
};
|
|
238
282
|
input.on('keypress', onKeypress);
|
|
239
283
|
});
|
|
240
284
|
|
|
241
|
-
if (onKeypress) input.off('keypress', onKeypress);
|
|
242
|
-
if (typeof input.setRawMode === 'function') input.setRawMode(false);
|
|
243
|
-
input.pause();
|
|
244
|
-
output.write('\n');
|
|
245
|
-
|
|
246
285
|
if (outcome.cancelled) {
|
|
247
286
|
return { selected: [], cancelled: true };
|
|
248
287
|
}
|
|
@@ -285,7 +324,7 @@ function parseRemoteSkillRef(skillRef) {
|
|
|
285
324
|
async function fetchRemoteMetadata(skillRef) {
|
|
286
325
|
const parsed = parseRemoteSkillRef(skillRef);
|
|
287
326
|
if (!parsed.lookupKeys.length) {
|
|
288
|
-
throw new Error('
|
|
327
|
+
throw new Error(t('ERR_REMOTE_INVALID'));
|
|
289
328
|
}
|
|
290
329
|
|
|
291
330
|
for (const key of parsed.lookupKeys) {
|
|
@@ -308,7 +347,7 @@ async function fetchRemoteMetadata(skillRef) {
|
|
|
308
347
|
}
|
|
309
348
|
|
|
310
349
|
throw new Error(
|
|
311
|
-
|
|
350
|
+
t('ERR_REMOTE_METADATA', { ref: skillRef })
|
|
312
351
|
);
|
|
313
352
|
}
|
|
314
353
|
|
|
@@ -319,12 +358,12 @@ async function downloadSkillFromRemote(skillRef, tmpDir) {
|
|
|
319
358
|
const sourceUrl = metadata.downloadUrl || metadata.sourceUrl || metadata.repo || metadata.url;
|
|
320
359
|
|
|
321
360
|
if (!sourceUrl) {
|
|
322
|
-
throw new Error(
|
|
361
|
+
throw new Error(t('ERR_REMOTE_NO_URL', { ref: skillRef }));
|
|
323
362
|
}
|
|
324
363
|
|
|
325
364
|
if (metadata.archiveUrl) {
|
|
326
365
|
const archiveResponse = await fetch(metadata.archiveUrl);
|
|
327
|
-
if (!archiveResponse.ok) throw new Error(
|
|
366
|
+
if (!archiveResponse.ok) throw new Error(t('ERR_REMOTE_DOWNLOAD', { status: archiveResponse.status }));
|
|
328
367
|
const archivePath = path.join(tmpDir, `${localName}.tgz`);
|
|
329
368
|
const buffer = Buffer.from(await archiveResponse.arrayBuffer());
|
|
330
369
|
await fs.writeFile(archivePath, buffer);
|
|
@@ -347,7 +386,7 @@ export async function installRemoteSkill(skillName, { cwd = process.cwd(), force
|
|
|
347
386
|
await ensureDir(projectSkillsDir);
|
|
348
387
|
|
|
349
388
|
if (/^https?:\/\/github\.com\//i.test(skillName)) {
|
|
350
|
-
throw new Error('
|
|
389
|
+
throw new Error(t('ERR_GITHUB_USAGE'));
|
|
351
390
|
}
|
|
352
391
|
|
|
353
392
|
const tempBase = await fs.mkdtemp(path.join(os.tmpdir(), 'skillbase-'));
|
|
@@ -355,7 +394,7 @@ export async function installRemoteSkill(skillName, { cwd = process.cwd(), force
|
|
|
355
394
|
const downloaded = await downloadSkillFromRemote(skillName, tempBase);
|
|
356
395
|
const target = path.join(projectSkillsDir, downloaded.localName);
|
|
357
396
|
if ((await exists(target)) && !force) {
|
|
358
|
-
throw new Error(
|
|
397
|
+
throw new Error(t('ERR_SKILL_EXISTS', { name: downloaded.localName }));
|
|
359
398
|
}
|
|
360
399
|
if (await exists(target)) await fs.rm(target, { recursive: true, force: true });
|
|
361
400
|
await copyDir(downloaded.path, target);
|
|
@@ -378,7 +417,7 @@ export async function installRemoteSkill(skillName, { cwd = process.cwd(), force
|
|
|
378
417
|
|
|
379
418
|
async function installRemoteFromGitHub(repoUrl, selectedSkill, { cwd = process.cwd(), force = false } = {}) {
|
|
380
419
|
if (!selectedSkill) {
|
|
381
|
-
throw new Error('
|
|
420
|
+
throw new Error(t('ERR_SKILL_REQUIRED'));
|
|
382
421
|
}
|
|
383
422
|
|
|
384
423
|
const projectSkillsDir = getProjectSkillsDir(cwd);
|
|
@@ -397,12 +436,12 @@ async function installRemoteFromGitHub(repoUrl, selectedSkill, { cwd = process.c
|
|
|
397
436
|
}
|
|
398
437
|
}
|
|
399
438
|
if (!sourcePath) {
|
|
400
|
-
throw new Error(
|
|
439
|
+
throw new Error(t('ERR_SKILL_NOT_FOUND_REMOTE', { name: selectedSkill }));
|
|
401
440
|
}
|
|
402
441
|
|
|
403
442
|
const target = path.join(projectSkillsDir, selectedSkill);
|
|
404
443
|
if ((await exists(target)) && !force) {
|
|
405
|
-
throw new Error(
|
|
444
|
+
throw new Error(t('ERR_SKILL_EXISTS', { name: selectedSkill }));
|
|
406
445
|
}
|
|
407
446
|
if (await exists(target)) await fs.rm(target, { recursive: true, force: true });
|
|
408
447
|
await copyDir(sourcePath, target);
|
|
@@ -433,7 +472,7 @@ export async function installRemoteSkillRef(skillRef, options = {}) {
|
|
|
433
472
|
export async function installFromManifest({ cwd = process.cwd(), remote = false, force = false } = {}) {
|
|
434
473
|
const manifest = await readManifest(cwd);
|
|
435
474
|
if (!manifest.skills.length) {
|
|
436
|
-
throw new Error('
|
|
475
|
+
throw new Error(t('ERR_MANIFEST_EMPTY'));
|
|
437
476
|
}
|
|
438
477
|
for (const skill of manifest.skills) {
|
|
439
478
|
if (remote || skill.source === 'remote') await installRemoteSkill(skill.name, { cwd, force });
|
|
@@ -442,12 +481,13 @@ export async function installFromManifest({ cwd = process.cwd(), remote = false,
|
|
|
442
481
|
}
|
|
443
482
|
|
|
444
483
|
export async function removeSkill(skillName, { cwd = process.cwd(), global = false } = {}) {
|
|
484
|
+
const safeName = sanitizeSkillName(skillName);
|
|
445
485
|
if (global) {
|
|
446
|
-
await fs.rm(path.join(getGlobalSkillsDir(),
|
|
486
|
+
await fs.rm(path.join(getGlobalSkillsDir(), safeName), { recursive: true, force: true });
|
|
447
487
|
return;
|
|
448
488
|
}
|
|
449
489
|
|
|
450
|
-
await fs.rm(path.join(getProjectSkillsDir(cwd),
|
|
490
|
+
await fs.rm(path.join(getProjectSkillsDir(cwd), safeName), { recursive: true, force: true });
|
|
451
491
|
const manifest = await readManifest(cwd);
|
|
452
492
|
removeSkillFromManifest(manifest, skillName);
|
|
453
493
|
await writeManifest(manifest, cwd);
|
|
@@ -542,8 +582,8 @@ export async function initProject({ cwd = process.cwd(), hard = false } = {}) {
|
|
|
542
582
|
|
|
543
583
|
const selection = await selectSkillsFromList(suggested, {
|
|
544
584
|
title: hard
|
|
545
|
-
? '
|
|
546
|
-
: '
|
|
585
|
+
? t('INIT_HARD_TITLE')
|
|
586
|
+
: t('INIT_TITLE')
|
|
547
587
|
});
|
|
548
588
|
if (selection.cancelled) return { technologies, suggested, installed: [], cancelled: true };
|
|
549
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
|
+
};
|
package/src/manifest.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
import { getManifestPath } from './config.js';
|
|
3
|
-
|
|
4
|
-
async function exists(target) {
|
|
5
|
-
try {
|
|
6
|
-
await fs.access(target);
|
|
7
|
-
return true;
|
|
8
|
-
} catch {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
2
|
+
import { getManifestPath, exists } from './config.js';
|
|
12
3
|
|
|
13
4
|
async function safeReadJson(file, fallback) {
|
|
14
5
|
if (!(await exists(file))) return fallback;
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
try {
|
|
7
|
+
const raw = await fs.readFile(file, 'utf8');
|
|
8
|
+
return JSON.parse(raw);
|
|
9
|
+
} catch (e) {
|
|
10
|
+
console.error(`Warning: Failed to parse JSON at ${file}. Using fallback.`);
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
export async function writeJson(file, data) {
|