@qubiit/lmagent 3.1.9 → 3.3.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/rules/00-master.md +5 -3
- package/.agents/templates/agent-configs/_generic.md +18 -3
- package/AGENTS.md +57 -35
- package/CLAUDE.md +34 -19
- package/GEMINI.md +34 -0
- package/README.md +17 -16
- package/install.js +1573 -1520
- package/package.json +85 -84
- package/scripts/create_skill.js +4 -4
- package/scripts/validate_skills.js +3 -3
- package/scripts/fix_descriptions.js +0 -51
package/install.js
CHANGED
|
@@ -1,1520 +1,1573 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const { Command } = require('commander');
|
|
7
|
-
const chalk = require('chalk');
|
|
8
|
-
const inquirer = require('inquirer');
|
|
9
|
-
const figlet = require('figlet');
|
|
10
|
-
const gradient = require('gradient-string');
|
|
11
|
-
|
|
12
|
-
const program = new Command();
|
|
13
|
-
const PKG_VERSION = require('./package.json').version;
|
|
14
|
-
|
|
15
|
-
// Configuración: Directorios fuente del paquete
|
|
16
|
-
const PACKAGE_SKILLS_DIR = path.join(__dirname, '.agents', 'skills');
|
|
17
|
-
const PACKAGE_RULES_DIR = path.join(__dirname, '.agents', 'rules');
|
|
18
|
-
const PACKAGE_WORKFLOWS_DIR = path.join(__dirname, '.agents', 'workflows');
|
|
19
|
-
const PACKAGE_CONFIG_DIR = path.join(__dirname, '.agents', 'config');
|
|
20
|
-
const PACKAGE_TEMPLATES_DIR = path.join(__dirname, '.agents', 'templates');
|
|
21
|
-
const PACKAGE_DOCS_DIR = path.join(__dirname, '.agents', 'docs');
|
|
22
|
-
const PACKAGE_MEMORY_DIR = path.join(__dirname, '.agents', 'memory');
|
|
23
|
-
|
|
24
|
-
// Archivos de proyecto que init copia a la raíz
|
|
25
|
-
// Usan {{VERSION}} como placeholder; se reemplaza dinámicamente al instalar
|
|
26
|
-
const INIT_FILES = [
|
|
27
|
-
{ src: '
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const INIT_DIRS = [
|
|
33
|
-
{ src: 'config', desc: 'Configuración del framework' },
|
|
34
|
-
{ src: 'templates', desc: 'Templates de proyecto' },
|
|
35
|
-
{ src: 'docs', desc: 'Documentación extendida' },
|
|
36
|
-
{ src: 'workflows', desc: 'SOPs y Procedimientos' },
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
// IDE_CONFIGS: Lista ÚNICA y DEDUPLICADA de todos los agentes soportados
|
|
40
|
-
const IDE_CONFIGS = [
|
|
41
|
-
// --- IDEs Principales (Auto-Detectados) ---
|
|
42
|
-
// Cursor: usa .cursor/rules/*.mdc (formato MDC con frontmatter)
|
|
43
|
-
{ name: 'Cursor', value: 'cursor', rulesDir: '.cursor/rules', skillsDir: '.cursor/rules/skills', workflowsDir: '.cursor/workflows', configFile:
|
|
44
|
-
// Windsurf Wave 8+: usa .windsurf/rules/*.md
|
|
45
|
-
{ name: 'Windsurf', value: 'windsurf', rulesDir: '.windsurf/rules', skillsDir: '.windsurf/skills', workflowsDir: '.windsurf/workflows', configFile: null, bridgeFile: 'lmagent.md', markerFile: '.windsurf', forceCopy: true },
|
|
46
|
-
// Cline: usa .clinerules/ (directorio con .md files)
|
|
47
|
-
{ name: 'Cline', value: 'cline', rulesDir: '.clinerules', skillsDir: '.cline/skills', workflowsDir: '.cline/workflows', configFile: null, bridgeFile: '00-lmagent.md', markerFile: '.clinerules', forceCopy: true },
|
|
48
|
-
// Roo Code: usa .roo/rules/
|
|
49
|
-
{ name: 'Roo Code', value: 'roo', rulesDir: '.roo/rules', skillsDir: '.roo/skills', workflowsDir: '.roo/workflows', configFile: null, bridgeFile: '00-lmagent.md', markerFile: '.roo', forceCopy: true },
|
|
50
|
-
// GitHub Copilot: usa .github/copilot-instructions.md + .github/instructions/*.md
|
|
51
|
-
{ name: 'VSCode Copilot', value: 'vscode', rulesDir: '.github/instructions', skillsDir: '.github/skills', workflowsDir: '.github/workflows', configFile: '.github/copilot-instructions.md', bridgeFile: null, markerFile: '.vscode' },
|
|
52
|
-
{ name: 'Trae', value: 'trae', rulesDir: '.trae/rules', skillsDir: '.trae/skills', workflowsDir: '.trae/workflows', configFile: null, bridgeFile: 'lmagent.md', markerFile: '.trae', forceCopy: true },
|
|
53
|
-
// Claude Code: usa
|
|
54
|
-
{ name: 'Claude Code', value: 'claude', rulesDir: '.claude/rules', skillsDir: '.claude/skills', workflowsDir: '.claude/workflows', configFile: 'CLAUDE.md', bridgeFile: null, markerFile: '.claude', forceCopy: true },
|
|
55
|
-
{ name: 'Zed', value: 'zed', rulesDir: '.rules', skillsDir: '.rules/skills', workflowsDir: '.rules/workflows', configFile: null, bridgeFile: 'lmagent.md', markerFile: '.zed' },
|
|
56
|
-
|
|
57
|
-
// --- Agentes CLI & Autónomos ---
|
|
58
|
-
{ name: 'Amp / Kimi / Replit', value: 'amp', rulesDir: '.agents/rules', skillsDir: '.agents/skills', workflowsDir: '.agents/workflows', configFile: null, bridgeFile: null, markerFile: '.agents' },
|
|
59
|
-
// Antigravity (Google Deepmind)
|
|
60
|
-
{ name: 'Antigravity', value: 'antigravity', rulesDir: '.agent/rules', skillsDir: '.agent/skills', workflowsDir: '.agent/workflows', configFile: 'GEMINI.md', bridgeFile: null, markerFile: '.agent' },
|
|
61
|
-
{ name: 'Augment', value: 'augment', rulesDir: '.augment/rules', skillsDir: '.augment/skills', workflowsDir: '.augment/workflows', configFile: null, bridgeFile: null, markerFile: '.augment' },
|
|
62
|
-
// Gemini CLI
|
|
63
|
-
{ name: 'Gemini CLI', value: 'gemini', rulesDir: '.gemini/rules', skillsDir: '.gemini/skills', workflowsDir: '.gemini/workflows', configFile: 'GEMINI.md', bridgeFile: null, markerFile: '.gemini' },
|
|
64
|
-
{ name: 'OpenClaw / Envoid', value: 'openclaw', rulesDir: 'rules', skillsDir: 'skills', workflowsDir: 'workflows', configFile: 'openclaw.json', configTemplate: 'openclaw.json', bridgeFile: null, markerFile: 'openclaw.json' },
|
|
65
|
-
{ name: 'CodeBuddy', value: 'codebuddy', rulesDir: '.codebuddy/rules', skillsDir: '.codebuddy/skills', workflowsDir: '.codebuddy/workflows', configFile: null, bridgeFile: null, markerFile: '.codebuddy', forceCopy: true },
|
|
66
|
-
// Codex CLI (OpenAI)
|
|
67
|
-
{ name: 'Codex', value: 'codex', rulesDir: '.codex', skillsDir: '.codex/skills', workflowsDir: '.codex/workflows', configFile: 'AGENTS.md', bridgeFile: null, markerFile: '.codex' },
|
|
68
|
-
{ name: 'Command Code', value: 'command-code', rulesDir: '.commandcode/rules', skillsDir: '.commandcode/skills', workflowsDir: '.commandcode/workflows', configFile: null, bridgeFile: null, markerFile: '.commandcode' },
|
|
69
|
-
// Continue
|
|
70
|
-
{ name: 'Continue', value: 'continue', rulesDir: '.continue/rules', skillsDir: '.continue/skills', workflowsDir: '.continue/workflows', configFile: '.continuerules', configTemplate: 'continuerules.md', bridgeFile: '00-lmagent.md', markerFile: '.continue' },
|
|
71
|
-
{ name: 'Crush', value: 'crush', rulesDir: '.crush/rules', skillsDir: '.crush/skills', workflowsDir: '.crush/workflows', configFile: null, bridgeFile: null, markerFile: '.crush' },
|
|
72
|
-
{ name: 'Droid', value: 'droid', rulesDir: '.factory/rules', skillsDir: '.factory/skills', workflowsDir: '.factory/workflows', configFile: null, bridgeFile: null, markerFile: '.factory' },
|
|
73
|
-
// Goose (Block)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
{ name: '
|
|
78
|
-
{ name: '
|
|
79
|
-
{ name: '
|
|
80
|
-
{ name: '
|
|
81
|
-
{ name: '
|
|
82
|
-
{ name: '
|
|
83
|
-
{ name: '
|
|
84
|
-
{ name: '
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
{ name: '
|
|
88
|
-
{ name: '
|
|
89
|
-
{ name: '
|
|
90
|
-
{ name: '
|
|
91
|
-
{ name: '
|
|
92
|
-
{ name: '
|
|
93
|
-
{ name: '
|
|
94
|
-
{ name: '
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{ name: '
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
.
|
|
109
|
-
.option('-
|
|
110
|
-
.option('-
|
|
111
|
-
.action((options) => {
|
|
112
|
-
runInstall(options);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
program.command('update')
|
|
116
|
-
.description('Actualizar skills y reglas en el proyecto (alias de install)')
|
|
117
|
-
.option('-f, --force', 'Forzar actualización')
|
|
118
|
-
.option('-y, --yes', 'Instalar todo sin preguntar')
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
.
|
|
127
|
-
.option('-
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
.
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.
|
|
167
|
-
.option('--
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
.argument('<
|
|
183
|
-
.
|
|
184
|
-
.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
console.log(chalk.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
.
|
|
232
|
-
.option('
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
console.log(gradient.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
//
|
|
243
|
-
const installedIdes = IDE_CONFIGS.filter(ide => {
|
|
244
|
-
if (ide.value === 'custom'
|
|
245
|
-
const markerInProject = ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile));
|
|
246
|
-
const rulesDirInProject = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir));
|
|
247
|
-
const skillsDirInProject = ide.skillsDir && fs.existsSync(path.join(projectRoot, ide.skillsDir));
|
|
248
|
-
return markerInProject || rulesDirInProject || skillsDirInProject;
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
function arePathsEqual(p1, p2) {
|
|
377
|
-
if (!p1 || !p2) return false;
|
|
378
|
-
return path.resolve(p1).toLowerCase() === path.resolve(p2).toLowerCase();
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Helper to deploy AGENTS.md, CLAUDE.md y GEMINI.md to project root
|
|
382
|
-
// Los archivos con versionTemplate:true tienen {{VERSION}} que se reemplaza dinámicamente
|
|
383
|
-
async function deployCorePillars(options, projectRoot) {
|
|
384
|
-
console.log(chalk.bold('\n🚀 Desplegando Pilares de Inteligencia (Contexto Root):'));
|
|
385
|
-
for (const file of INIT_FILES) {
|
|
386
|
-
const srcPath = path.join(__dirname, file.src);
|
|
387
|
-
const destPath = path.join(projectRoot, file.src);
|
|
388
|
-
|
|
389
|
-
if (fs.existsSync(srcPath)) {
|
|
390
|
-
let shouldCopy = false;
|
|
391
|
-
if (!fs.existsSync(destPath)) {
|
|
392
|
-
shouldCopy = true;
|
|
393
|
-
console.log(` ${chalk.green('✔')} ${file.src} (Creado en la raíz)`);
|
|
394
|
-
} else {
|
|
395
|
-
// Si ya existe pero tiene versión vieja, actualizar automáticamente
|
|
396
|
-
const existingContent = fs.readFileSync(destPath, 'utf8');
|
|
397
|
-
const hasOldVersion = existingContent.includes('{{VERSION}}') ||
|
|
398
|
-
(file.versionTemplate && !existingContent.includes(`v${PKG_VERSION}`));
|
|
399
|
-
|
|
400
|
-
if (options.force || hasOldVersion) {
|
|
401
|
-
shouldCopy = true;
|
|
402
|
-
const reason = hasOldVersion ? 'Actualizando versión' : 'Sobrescribiendo por --force';
|
|
403
|
-
console.log(` ${chalk.yellow('✎')} ${file.src} (${reason})`);
|
|
404
|
-
} else if (options.yes) {
|
|
405
|
-
console.log(` ${chalk.blue('ℹ')} ${file.src} ya existe v${PKG_VERSION} (OK)`);
|
|
406
|
-
} else {
|
|
407
|
-
const answer = await inquirer.prompt([{
|
|
408
|
-
type: 'confirm',
|
|
409
|
-
name: 'overwrite',
|
|
410
|
-
message: `⚠️ ${file.src} ya existe. ¿Deseas actualizarlo?`,
|
|
411
|
-
default: false
|
|
412
|
-
}]);
|
|
413
|
-
shouldCopy = answer.overwrite;
|
|
414
|
-
if (shouldCopy) console.log(` ${chalk.yellow('✎')} ${file.src} (Actualizado)`);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (shouldCopy) {
|
|
419
|
-
let content = fs.readFileSync(srcPath, 'utf8');
|
|
420
|
-
// Inyectar versión dinámica si el archivo usa template
|
|
421
|
-
if (file.versionTemplate) {
|
|
422
|
-
content = content.replace(/\{\{VERSION\}\}/g, PKG_VERSION);
|
|
423
|
-
}
|
|
424
|
-
fs.writeFileSync(destPath, content, 'utf8');
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
async function runInstall(options) {
|
|
431
|
-
console.clear();
|
|
432
|
-
const branding = figlet.textSync('LMAGENT', { font: 'ANSI Shadow' });
|
|
433
|
-
console.log(gradient.pastel.multiline(branding));
|
|
434
|
-
console.log(gradient.cristal(' by QuBit\n'));
|
|
435
|
-
|
|
436
|
-
const projectRoot = process.cwd();
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
const
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
'
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
'
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
'
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
//
|
|
552
|
-
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
{
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
console.log(chalk.
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
fs.
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
console.log(chalk.
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
if (
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
} else {
|
|
1516
|
-
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { Command } = require('commander');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const figlet = require('figlet');
|
|
10
|
+
const gradient = require('gradient-string');
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
const PKG_VERSION = require('./package.json').version;
|
|
14
|
+
|
|
15
|
+
// Configuración: Directorios fuente del paquete
|
|
16
|
+
const PACKAGE_SKILLS_DIR = path.join(__dirname, '.agents', 'skills');
|
|
17
|
+
const PACKAGE_RULES_DIR = path.join(__dirname, '.agents', 'rules');
|
|
18
|
+
const PACKAGE_WORKFLOWS_DIR = path.join(__dirname, '.agents', 'workflows');
|
|
19
|
+
const PACKAGE_CONFIG_DIR = path.join(__dirname, '.agents', 'config');
|
|
20
|
+
const PACKAGE_TEMPLATES_DIR = path.join(__dirname, '.agents', 'templates');
|
|
21
|
+
const PACKAGE_DOCS_DIR = path.join(__dirname, '.agents', 'docs');
|
|
22
|
+
const PACKAGE_MEMORY_DIR = path.join(__dirname, '.agents', 'memory');
|
|
23
|
+
|
|
24
|
+
// Archivos de proyecto que init copia a la raíz
|
|
25
|
+
// Usan {{VERSION}} como placeholder; se reemplaza dinámicamente al instalar
|
|
26
|
+
const INIT_FILES = [
|
|
27
|
+
{ src: 'AGENTS.md', desc: 'Catálogo de capacidades LMAgent (Entry Point Universal)', versionTemplate: false },
|
|
28
|
+
// CLAUDE.md y GEMINI.md NO van aquí — se despliegan solo cuando su agente está detectado/seleccionado,
|
|
29
|
+
// para evitar conflictos de contexto duplicado en agentes como Cursor y Zed que leen múltiples .md del raíz.
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const INIT_DIRS = [
|
|
33
|
+
{ src: 'config', desc: 'Configuración del framework' },
|
|
34
|
+
{ src: 'templates', desc: 'Templates de proyecto' },
|
|
35
|
+
{ src: 'docs', desc: 'Documentación extendida' },
|
|
36
|
+
{ src: 'workflows', desc: 'SOPs y Procedimientos' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// IDE_CONFIGS: Lista ÚNICA y DEDUPLICADA de todos los agentes soportados
|
|
40
|
+
const IDE_CONFIGS = [
|
|
41
|
+
// --- IDEs Principales (Auto-Detectados) ---
|
|
42
|
+
// Cursor: usa .cursor/rules/*.mdc (formato MDC con frontmatter)
|
|
43
|
+
{ name: 'Cursor', value: 'cursor', rulesDir: '.cursor/rules', skillsDir: '.cursor/rules/skills', workflowsDir: '.cursor/workflows', configFile: null, bridgeFile: '00-lmagent.mdc', markerFile: '.cursorrules', forceCopy: true },
|
|
44
|
+
// Windsurf Wave 8+: usa .windsurf/rules/*.md
|
|
45
|
+
{ name: 'Windsurf', value: 'windsurf', rulesDir: '.windsurf/rules', skillsDir: '.windsurf/skills', workflowsDir: '.windsurf/workflows', configFile: null, bridgeFile: 'lmagent.md', markerFile: '.windsurf', forceCopy: true },
|
|
46
|
+
// Cline: usa .clinerules/ (directorio con .md files)
|
|
47
|
+
{ name: 'Cline', value: 'cline', rulesDir: '.clinerules', skillsDir: '.cline/skills', workflowsDir: '.cline/workflows', configFile: null, bridgeFile: '00-lmagent.md', markerFile: '.clinerules', forceCopy: true },
|
|
48
|
+
// Roo Code: usa .roo/rules/
|
|
49
|
+
{ name: 'Roo Code', value: 'roo', rulesDir: '.roo/rules', skillsDir: '.roo/skills', workflowsDir: '.roo/workflows', configFile: null, bridgeFile: '00-lmagent.md', markerFile: '.roo', forceCopy: true },
|
|
50
|
+
// GitHub Copilot: usa .github/copilot-instructions.md + .github/instructions/*.md
|
|
51
|
+
{ name: 'VSCode Copilot', value: 'vscode', rulesDir: '.github/instructions', skillsDir: '.github/skills', workflowsDir: '.github/copilot-workflows', configFile: '.github/copilot-instructions.md', bridgeFile: null, markerFile: '.vscode' },
|
|
52
|
+
{ name: 'Trae', value: 'trae', rulesDir: '.trae/rules', skillsDir: '.trae/skills', workflowsDir: '.trae/workflows', configFile: null, bridgeFile: 'lmagent.md', markerFile: '.trae', forceCopy: true },
|
|
53
|
+
// Claude Code: usa .claude/
|
|
54
|
+
{ name: 'Claude Code', value: 'claude', rulesDir: '.claude/rules', skillsDir: '.claude/skills', workflowsDir: '.claude/workflows', configFile: 'CLAUDE.md', bridgeFile: null, markerFile: '.claude', forceCopy: true },
|
|
55
|
+
{ name: 'Zed', value: 'zed', rulesDir: '.rules', skillsDir: '.rules/skills', workflowsDir: '.rules/workflows', configFile: null, bridgeFile: 'lmagent.md', markerFile: '.zed' },
|
|
56
|
+
|
|
57
|
+
// --- Agentes CLI & Autónomos ---
|
|
58
|
+
{ name: 'Amp / Kimi / Replit', value: 'amp', rulesDir: '.agents/rules', skillsDir: '.agents/skills', workflowsDir: '.agents/workflows', configFile: null, bridgeFile: null, markerFile: '.agents' },
|
|
59
|
+
// Antigravity (Google Deepmind)
|
|
60
|
+
{ name: 'Antigravity', value: 'antigravity', rulesDir: '.agent/rules', skillsDir: '.agent/skills', workflowsDir: '.agent/workflows', configFile: 'GEMINI.md', bridgeFile: null, markerFile: '.agent' },
|
|
61
|
+
{ name: 'Augment', value: 'augment', rulesDir: '.augment/rules', skillsDir: '.augment/skills', workflowsDir: '.augment/workflows', configFile: null, bridgeFile: null, markerFile: '.augment' },
|
|
62
|
+
// Gemini CLI
|
|
63
|
+
{ name: 'Gemini CLI', value: 'gemini', rulesDir: '.gemini/rules', skillsDir: '.gemini/skills', workflowsDir: '.gemini/workflows', configFile: 'GEMINI.md', bridgeFile: null, markerFile: '.gemini' },
|
|
64
|
+
{ name: 'OpenClaw / Envoid', value: 'openclaw', rulesDir: 'rules', skillsDir: 'skills', workflowsDir: 'workflows', configFile: 'openclaw.json', configTemplate: 'openclaw.json', bridgeFile: null, markerFile: 'openclaw.json' },
|
|
65
|
+
{ name: 'CodeBuddy', value: 'codebuddy', rulesDir: '.codebuddy/rules', skillsDir: '.codebuddy/skills', workflowsDir: '.codebuddy/workflows', configFile: null, bridgeFile: null, markerFile: '.codebuddy', forceCopy: true },
|
|
66
|
+
// Codex CLI (OpenAI)
|
|
67
|
+
{ name: 'Codex', value: 'codex', rulesDir: '.codex', skillsDir: '.codex/skills', workflowsDir: '.codex/workflows', configFile: 'AGENTS.md', bridgeFile: null, markerFile: '.codex' },
|
|
68
|
+
{ name: 'Command Code', value: 'command-code', rulesDir: '.commandcode/rules', skillsDir: '.commandcode/skills', workflowsDir: '.commandcode/workflows', configFile: null, bridgeFile: null, markerFile: '.commandcode' },
|
|
69
|
+
// Continue
|
|
70
|
+
{ name: 'Continue', value: 'continue', rulesDir: '.continue/rules', skillsDir: '.continue/skills', workflowsDir: '.continue/workflows', configFile: '.continue/continuerules', configTemplate: 'continuerules.md', bridgeFile: '00-lmagent.md', markerFile: '.continue' },
|
|
71
|
+
{ name: 'Crush', value: 'crush', rulesDir: '.crush/rules', skillsDir: '.crush/skills', workflowsDir: '.crush/workflows', configFile: null, bridgeFile: null, markerFile: '.crush' },
|
|
72
|
+
{ name: 'Droid', value: 'droid', rulesDir: '.factory/rules', skillsDir: '.factory/skills', workflowsDir: '.factory/workflows', configFile: null, bridgeFile: null, markerFile: '.factory' },
|
|
73
|
+
// Goose (Block)
|
|
74
|
+
// Goose: .goosehints va en la RAÍZ del proyecto (no en .goose/), según docs oficiales
|
|
75
|
+
{ name: 'Goose', value: 'goose', rulesDir: '.goose', skillsDir: '.goose/skills', workflowsDir: '.goose/workflows', configFile: '.goosehints', configTemplate: 'goosehints.md', bridgeFile: null, markerFile: '.goose' },
|
|
76
|
+
// Junie (JetBrains)
|
|
77
|
+
{ name: 'Junie', value: 'junie', rulesDir: '.junie', skillsDir: '.junie/skills', workflowsDir: '.junie/workflows', configFile: '.junie/guidelines.md', configTemplate: 'junie-guidelines.md', bridgeFile: null, markerFile: '.junie' },
|
|
78
|
+
{ name: 'iFlow CLI', value: 'iflow', rulesDir: '.iflow/rules', skillsDir: '.iflow/skills', workflowsDir: '.iflow/workflows', configFile: null, bridgeFile: null, markerFile: '.iflow' },
|
|
79
|
+
{ name: 'Kilo Code', value: 'kilo', rulesDir: '.kilocode/rules', skillsDir: '.kilocode/skills', workflowsDir: '.kilocode/workflows', configFile: null, bridgeFile: null, markerFile: '.kilocode' },
|
|
80
|
+
{ name: 'Kiro CLI', value: 'kiro', rulesDir: '.kiro/rules', skillsDir: '.kiro/skills', workflowsDir: '.kiro/workflows', configFile: null, bridgeFile: null, markerFile: '.kiro' },
|
|
81
|
+
{ name: 'Kode', value: 'kode', rulesDir: '.kode/rules', skillsDir: '.kode/skills', workflowsDir: '.kode/workflows', configFile: null, bridgeFile: null, markerFile: '.kode' },
|
|
82
|
+
{ name: 'MCPJam', value: 'mcpjam', rulesDir: '.mcpjam/rules', skillsDir: '.mcpjam/skills', workflowsDir: '.mcpjam/workflows', configFile: null, bridgeFile: null, markerFile: '.mcpjam' },
|
|
83
|
+
{ name: 'Mistral Vibe', value: 'mistral', rulesDir: '.vibe/rules', skillsDir: '.vibe/skills', workflowsDir: '.vibe/workflows', configFile: null, bridgeFile: null, markerFile: '.vibe' },
|
|
84
|
+
{ name: 'Mux', value: 'mux', rulesDir: '.mux/rules', skillsDir: '.mux/skills', workflowsDir: '.mux/workflows', configFile: null, bridgeFile: null, markerFile: '.mux' },
|
|
85
|
+
{ name: 'OpenCode', value: 'opencode', rulesDir: '.opencode/rules', skillsDir: '.opencode/skills', workflowsDir: '.opencode/workflows', configFile: null, bridgeFile: null, markerFile: '.opencode' },
|
|
86
|
+
// OpenHands: usa .openhands/microagents/repo.md
|
|
87
|
+
{ name: 'OpenHands', value: 'openhands', rulesDir: '.openhands/microagents', skillsDir: '.openhands/skills', workflowsDir: '.openhands/workflows', configFile: '.openhands/microagents/repo.md', configTemplate: 'openhands-repo.md', bridgeFile: null, markerFile: '.openhands' },
|
|
88
|
+
{ name: 'Pi', value: 'pi', rulesDir: '.pi/rules', skillsDir: '.pi/skills', workflowsDir: '.pi/workflows', configFile: null, bridgeFile: null, markerFile: '.pi' },
|
|
89
|
+
{ name: 'Qoder', value: 'qoder', rulesDir: '.qoder/rules', skillsDir: '.qoder/skills', workflowsDir: '.qoder/workflows', configFile: null, bridgeFile: null, markerFile: '.qoder' },
|
|
90
|
+
{ name: 'Qwen Code', value: 'qwen', rulesDir: '.qwen/rules', skillsDir: '.qwen/skills', workflowsDir: '.qwen/workflows', configFile: null, bridgeFile: null, markerFile: '.qwen' },
|
|
91
|
+
{ name: 'Trae CN', value: 'trae-cn', rulesDir: '.trae-cn/rules', skillsDir: '.trae-cn/skills', workflowsDir: '.trae-cn/workflows', configFile: null, bridgeFile: 'lmagent.md', markerFile: '.trae-cn' },
|
|
92
|
+
{ name: 'Zencoder', value: 'zencoder', rulesDir: '.zencoder/rules', skillsDir: '.zencoder/skills', workflowsDir: '.zencoder/workflows', configFile: null, bridgeFile: null, markerFile: '.zencoder' },
|
|
93
|
+
{ name: 'Neovate', value: 'neovate', rulesDir: '.neovate/rules', skillsDir: '.neovate/skills', workflowsDir: '.neovate/workflows', configFile: null, bridgeFile: null, markerFile: '.neovate' },
|
|
94
|
+
{ name: 'Pochi', value: 'pochi', rulesDir: '.pochi/rules', skillsDir: '.pochi/skills', workflowsDir: '.pochi/workflows', configFile: null, bridgeFile: null, markerFile: '.pochi' },
|
|
95
|
+
{ name: 'AdaL', value: 'adal', rulesDir: '.adal/rules', skillsDir: '.adal/skills', workflowsDir: '.adal/workflows', configFile: null, bridgeFile: null, markerFile: '.adal' },
|
|
96
|
+
|
|
97
|
+
// --- Opciones Especiales ---
|
|
98
|
+
{ name: 'Generic/Other', value: 'generic', rulesDir: '.agents/rules', skillsDir: '.agents/skills', workflowsDir: '.agents/workflows', configFile: null, bridgeFile: null, markerFile: '.agents' },
|
|
99
|
+
{ name: 'Custom Path (Manual)', value: 'custom', rulesDir: '', skillsDir: '', workflowsDir: '', configFile: null, bridgeFile: null, markerFile: '' },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
program
|
|
103
|
+
.name('lmagent')
|
|
104
|
+
.description('CLI para instalar skills y reglas de LMAgent')
|
|
105
|
+
.version(PKG_VERSION);
|
|
106
|
+
|
|
107
|
+
program.command('install')
|
|
108
|
+
.description('Instalar skills, rules y workflows en el IDE del proyecto')
|
|
109
|
+
.option('-f, --force', 'Forzar instalación')
|
|
110
|
+
.option('-y, --yes', 'Instalar todo sin preguntar')
|
|
111
|
+
.action((options) => {
|
|
112
|
+
runInstall(options);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
program.command('update')
|
|
116
|
+
.description('Actualizar skills y reglas en el proyecto (alias de install)')
|
|
117
|
+
.option('-f, --force', 'Forzar actualización')
|
|
118
|
+
.option('-y, --yes', 'Instalar todo sin preguntar')
|
|
119
|
+
.action((options) => {
|
|
120
|
+
console.log(chalk.blue('ℹ Iniciando actualización...'));
|
|
121
|
+
runInstall(options);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
program.command('init')
|
|
125
|
+
.description('Inicializar proyecto con LMAgent (copia AGENTS.md y estructura base)')
|
|
126
|
+
.option('-f, --force', 'Sobrescribir archivos existentes')
|
|
127
|
+
.option('-y, --yes', 'No preguntar, instalar todo')
|
|
128
|
+
.action((options) => {
|
|
129
|
+
runInit(options);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
program.command('doctor')
|
|
133
|
+
.description('Verificar que el proyecto está correctamente configurado')
|
|
134
|
+
.action(() => {
|
|
135
|
+
runDoctor();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
program.command('validate')
|
|
139
|
+
.description('Validar integridad de todos los skills (frontmatter, estructura)')
|
|
140
|
+
.argument('[skill]', 'Nombre parcial del skill a validar (opcional)')
|
|
141
|
+
.action((skill) => {
|
|
142
|
+
const { execSync } = require('child_process');
|
|
143
|
+
const scriptPath = path.join(__dirname, 'scripts', 'validate_skills.js');
|
|
144
|
+
const args = skill ? ` ${skill}` : '';
|
|
145
|
+
try {
|
|
146
|
+
execSync(`node "${scriptPath}"${args}`, { stdio: 'inherit' });
|
|
147
|
+
} catch (e) {
|
|
148
|
+
process.exit(e.status || 1);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
program.command('create-skill')
|
|
153
|
+
.description('Crear un nuevo skill interactivamente')
|
|
154
|
+
.action(() => {
|
|
155
|
+
const { execSync } = require('child_process');
|
|
156
|
+
const scriptPath = path.join(__dirname, 'scripts', 'create_skill.js');
|
|
157
|
+
try {
|
|
158
|
+
execSync(`node "${scriptPath}"`, { stdio: 'inherit' });
|
|
159
|
+
} catch (e) {
|
|
160
|
+
process.exit(e.status || 1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
program.command('tokens')
|
|
165
|
+
.description('Analizar consumo de tokens del framework instalado en el proyecto')
|
|
166
|
+
.option('--json', 'Salida en formato JSON')
|
|
167
|
+
.option('--report', 'Generar reporte en .agents/token-report.md')
|
|
168
|
+
.action((options) => {
|
|
169
|
+
const { execSync } = require('child_process');
|
|
170
|
+
const scriptPath = path.join(__dirname, 'scripts', 'token-analyzer.js');
|
|
171
|
+
const args = [options.json ? '--json' : '', options.report ? '--report' : ''].filter(Boolean).join(' ');
|
|
172
|
+
try {
|
|
173
|
+
execSync(`node "${scriptPath}" ${args}`, { stdio: 'inherit' });
|
|
174
|
+
} catch (e) {
|
|
175
|
+
process.exit(e.status || 1);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
program.command('skills')
|
|
180
|
+
.description('Gestionar skills externos desde GitHub (compatible con el estándar skills.sh)')
|
|
181
|
+
.argument('<action>', 'Acción: add')
|
|
182
|
+
.argument('<source>', 'Repositorio GitHub: owner/repo o URL completa')
|
|
183
|
+
.option('--skill <name>', 'Nombre específico del skill a instalar (opcional)')
|
|
184
|
+
.action(async (action, source, opts) => {
|
|
185
|
+
if (action !== 'add') {
|
|
186
|
+
console.error(chalk.red(`❌ Acción desconocida: ${action}. Usa: lmagent skills add <owner/repo>`));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const { execSync } = require('child_process');
|
|
190
|
+
const repoSlug = source.replace('https://github.com/', '').replace(/\.git$/, '');
|
|
191
|
+
const [owner, repo] = repoSlug.split('/');
|
|
192
|
+
if (!owner || !repo) {
|
|
193
|
+
console.error(chalk.red('❌ Formato inválido. Usa: lmagent skills add owner/repo'));
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
const tmpDir = path.join(os.tmpdir(), `lmagent-skill-${Date.now()}`);
|
|
197
|
+
const targetSkillsDir = path.join(process.cwd(), '.agents', 'skills');
|
|
198
|
+
console.log(chalk.cyan(`📦 Descargando skill desde github.com/${owner}/${repo}...`));
|
|
199
|
+
try {
|
|
200
|
+
execSync(`git clone --depth 1 https://github.com/${owner}/${repo} "${tmpDir}"`, { stdio: 'pipe' });
|
|
201
|
+
const skillsPath = fs.existsSync(path.join(tmpDir, 'skills')) ? path.join(tmpDir, 'skills') : tmpDir;
|
|
202
|
+
const items = fs.readdirSync(skillsPath).filter(i => {
|
|
203
|
+
const p = path.join(skillsPath, i);
|
|
204
|
+
return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md'));
|
|
205
|
+
});
|
|
206
|
+
if (items.length === 0) {
|
|
207
|
+
console.log(chalk.yellow('⚠️ No se encontraron skills con SKILL.md en el repositorio.'));
|
|
208
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const toInstall = opts.skill ? items.filter(i => i.includes(opts.skill)) : items;
|
|
212
|
+
if (!fs.existsSync(targetSkillsDir)) fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
213
|
+
for (const skill of toInstall) {
|
|
214
|
+
const src = path.join(skillsPath, skill);
|
|
215
|
+
const dest = path.join(targetSkillsDir, skill);
|
|
216
|
+
copyRecursiveSync(src, dest, true);
|
|
217
|
+
console.log(` ${chalk.green('✔')} ${skill}/`);
|
|
218
|
+
}
|
|
219
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
220
|
+
console.log(chalk.green(`✨ ${toInstall.length} skill(s) instalado(s) en .agents/skills/`));
|
|
221
|
+
console.log(chalk.dim(' Ejecuta `lmagent install` para sincronizarlos a tu agente.'));
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.error(chalk.red(`❌ Error al instalar skill: ${e.message}`));
|
|
224
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (_) { }
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
program.command('uninstall')
|
|
230
|
+
.description('Eliminar todos los archivos instalados por LMAgent del proyecto')
|
|
231
|
+
.option('-f, --force', 'No pedir confirmación, eliminar directamente')
|
|
232
|
+
.option('--all', 'También eliminar entry points raíz (CLAUDE.md, GEMINI.md, AGENTS.md)')
|
|
233
|
+
.action(async (options) => {
|
|
234
|
+
console.clear();
|
|
235
|
+
const branding = figlet.textSync('LMAGENT', { font: 'ANSI Shadow' });
|
|
236
|
+
console.log(gradient.pastel.multiline(branding));
|
|
237
|
+
console.log(gradient.cristal(' Uninstall - Limpieza\n'));
|
|
238
|
+
|
|
239
|
+
const projectRoot = process.cwd();
|
|
240
|
+
|
|
241
|
+
// Detectar qué agentes están instalados o dejaron rastro en el proyecto
|
|
242
|
+
// Al desinstalar, también incluimos 'generic' para volar .agents/
|
|
243
|
+
const installedIdes = IDE_CONFIGS.filter(ide => {
|
|
244
|
+
if (ide.value === 'custom') return false;
|
|
245
|
+
const markerInProject = ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile));
|
|
246
|
+
const rulesDirInProject = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir));
|
|
247
|
+
const skillsDirInProject = ide.skillsDir && fs.existsSync(path.join(projectRoot, ide.skillsDir));
|
|
248
|
+
return markerInProject || rulesDirInProject || skillsDirInProject || ide.value === 'generic';
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const rootEntryFiles = ['CLAUDE.md', 'GEMINI.md', 'AGENTS.md', '.cursorrules', '.windsurfrules', '.windsurfrules.md', '.continuerules', '.goosehints', 'openclaw.json'];
|
|
252
|
+
const existingRootFiles = rootEntryFiles.filter(f => fs.existsSync(path.join(projectRoot, f)));
|
|
253
|
+
|
|
254
|
+
if (installedIdes.length === 0 && existingRootFiles.length === 0) {
|
|
255
|
+
console.log(chalk.yellow('⚠️ No se detectó ningún rastro del framework en este proyecto.'));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log(chalk.bold('🔍 Agentes detectados en este proyecto:\n'));
|
|
260
|
+
for (const ide of installedIdes) {
|
|
261
|
+
const parts = [];
|
|
262
|
+
if (ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir))) parts.push(chalk.gray(ide.rulesDir));
|
|
263
|
+
if (ide.skillsDir && fs.existsSync(path.join(projectRoot, ide.skillsDir))) parts.push(chalk.gray(ide.skillsDir));
|
|
264
|
+
if (ide.configFile && fs.existsSync(path.join(projectRoot, ide.configFile))) parts.push(chalk.gray(ide.configFile));
|
|
265
|
+
console.log(` ${chalk.cyan('•')} ${chalk.bold(ide.name)}: ${parts.join(', ')}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Entry points raíz (ya calculados arriba como rootEntryFiles)
|
|
269
|
+
|
|
270
|
+
if (options.all && existingRootFiles.length > 0) {
|
|
271
|
+
console.log(chalk.bold('\n📄 Entry points raíz que también se eliminarán:'));
|
|
272
|
+
for (const f of existingRootFiles) {
|
|
273
|
+
console.log(` ${chalk.red('•')} ${f}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log('');
|
|
278
|
+
|
|
279
|
+
if (!options.force) {
|
|
280
|
+
const { confirm } = await inquirer.prompt([{
|
|
281
|
+
type: 'confirm',
|
|
282
|
+
name: 'confirm',
|
|
283
|
+
message: chalk.red(`⚠️ ¿Eliminar todos los archivos de LMAgent de este proyecto? Esta acción no se puede deshacer.`),
|
|
284
|
+
default: false
|
|
285
|
+
}]);
|
|
286
|
+
if (!confirm) {
|
|
287
|
+
console.log(chalk.gray('Cancelado.'));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log('');
|
|
293
|
+
let removed = 0;
|
|
294
|
+
let errors = 0;
|
|
295
|
+
|
|
296
|
+
// Eliminar directorios y archivos de cada agente
|
|
297
|
+
for (const ide of installedIdes) {
|
|
298
|
+
const toRemove = [];
|
|
299
|
+
|
|
300
|
+
// Directorios del agente (skills, rules, workflows) — solo si son específicos del agente
|
|
301
|
+
// No eliminar directorios genéricos como .github, rules/, skills/ que pueden tener otros usos
|
|
302
|
+
const agentSpecificDirs = [ide.skillsDir, ide.workflowsDir];
|
|
303
|
+
if (ide.rulesDir && !['rules', '.github/instructions'].includes(ide.rulesDir)) {
|
|
304
|
+
agentSpecificDirs.push(ide.rulesDir);
|
|
305
|
+
}
|
|
306
|
+
if (ide.value === 'generic') {
|
|
307
|
+
agentSpecificDirs.push('.agents');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Determinar el directorio raíz del agente (ej: .cursor, .windsurf, .claude)
|
|
311
|
+
const agentRootDir = ide.markerFile && !ide.markerFile.includes('.') ? null
|
|
312
|
+
: ide.rulesDir ? ide.rulesDir.split('/')[0] : null;
|
|
313
|
+
if (agentRootDir && agentRootDir.startsWith('.') && fs.existsSync(path.join(projectRoot, agentRootDir))) {
|
|
314
|
+
// Eliminar el directorio raíz completo del agente
|
|
315
|
+
toRemove.push({ path: path.join(projectRoot, agentRootDir), type: 'dir', label: agentRootDir + '/' });
|
|
316
|
+
} else {
|
|
317
|
+
// Eliminar subdirectorios individualmente
|
|
318
|
+
for (const dir of agentSpecificDirs) {
|
|
319
|
+
if (dir && fs.existsSync(path.join(projectRoot, dir))) {
|
|
320
|
+
toRemove.push({ path: path.join(projectRoot, dir), type: 'dir', label: dir + '/' });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Archivos de configuración específicos del agente (markerFile si es un archivo)
|
|
326
|
+
if (ide.markerFile && ide.markerFile.includes('.') && fs.existsSync(path.join(projectRoot, ide.markerFile))) {
|
|
327
|
+
const markerStat = fs.statSync(path.join(projectRoot, ide.markerFile));
|
|
328
|
+
if (markerStat.isFile()) {
|
|
329
|
+
toRemove.push({ path: path.join(projectRoot, ide.markerFile), type: 'file', label: ide.markerFile });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for (const item of toRemove) {
|
|
334
|
+
try {
|
|
335
|
+
if (item.type === 'dir') {
|
|
336
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
337
|
+
} else {
|
|
338
|
+
fs.unlinkSync(item.path);
|
|
339
|
+
}
|
|
340
|
+
console.log(` ${chalk.red('✘')} ${ide.name}: ${chalk.gray(item.label)} eliminado`);
|
|
341
|
+
removed++;
|
|
342
|
+
} catch (e) {
|
|
343
|
+
console.error(` ${chalk.red('❌')} Error eliminando ${item.label}: ${e.message}`);
|
|
344
|
+
errors++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Eliminar entry points raíz si --all
|
|
350
|
+
if (options.all) {
|
|
351
|
+
for (const f of existingRootFiles) {
|
|
352
|
+
try {
|
|
353
|
+
fs.unlinkSync(path.join(projectRoot, f));
|
|
354
|
+
console.log(` ${chalk.red('✘')} ${chalk.gray(f)} eliminado`);
|
|
355
|
+
removed++;
|
|
356
|
+
} catch (e) {
|
|
357
|
+
console.error(` ${chalk.red('❌')} Error eliminando ${f}: ${e.message}`);
|
|
358
|
+
errors++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
console.log('');
|
|
364
|
+
if (errors === 0) {
|
|
365
|
+
console.log(gradient.pastel(`\n✨ Limpieza completada. ${removed} elemento(s) eliminado(s).\n`));
|
|
366
|
+
if (!options.all) {
|
|
367
|
+
console.log(chalk.dim(' 💡 Usa `lmagent uninstall --all` para también eliminar CLAUDE.md, GEMINI.md y AGENTS.md.'));
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
console.log(chalk.yellow(`\n⚠️ ${removed} eliminado(s), ${errors} error(es).\n`));
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// CLI entry point is handled at the bottom of the file (require.main === module)
|
|
375
|
+
|
|
376
|
+
function arePathsEqual(p1, p2) {
|
|
377
|
+
if (!p1 || !p2) return false;
|
|
378
|
+
return path.resolve(p1).toLowerCase() === path.resolve(p2).toLowerCase();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Helper to deploy AGENTS.md, CLAUDE.md y GEMINI.md to project root
|
|
382
|
+
// Los archivos con versionTemplate:true tienen {{VERSION}} que se reemplaza dinámicamente
|
|
383
|
+
async function deployCorePillars(options, projectRoot) {
|
|
384
|
+
console.log(chalk.bold('\n🚀 Desplegando Pilares de Inteligencia (Contexto Root):'));
|
|
385
|
+
for (const file of INIT_FILES) {
|
|
386
|
+
const srcPath = path.join(__dirname, file.src);
|
|
387
|
+
const destPath = path.join(projectRoot, file.src);
|
|
388
|
+
|
|
389
|
+
if (fs.existsSync(srcPath)) {
|
|
390
|
+
let shouldCopy = false;
|
|
391
|
+
if (!fs.existsSync(destPath)) {
|
|
392
|
+
shouldCopy = true;
|
|
393
|
+
console.log(` ${chalk.green('✔')} ${file.src} (Creado en la raíz)`);
|
|
394
|
+
} else {
|
|
395
|
+
// Si ya existe pero tiene versión vieja, actualizar automáticamente
|
|
396
|
+
const existingContent = fs.readFileSync(destPath, 'utf8');
|
|
397
|
+
const hasOldVersion = existingContent.includes('{{VERSION}}') ||
|
|
398
|
+
(file.versionTemplate && !existingContent.includes(`v${PKG_VERSION}`));
|
|
399
|
+
|
|
400
|
+
if (options.force || hasOldVersion) {
|
|
401
|
+
shouldCopy = true;
|
|
402
|
+
const reason = hasOldVersion ? 'Actualizando versión' : 'Sobrescribiendo por --force';
|
|
403
|
+
console.log(` ${chalk.yellow('✎')} ${file.src} (${reason})`);
|
|
404
|
+
} else if (options.yes) {
|
|
405
|
+
console.log(` ${chalk.blue('ℹ')} ${file.src} ya existe v${PKG_VERSION} (OK)`);
|
|
406
|
+
} else {
|
|
407
|
+
const answer = await inquirer.prompt([{
|
|
408
|
+
type: 'confirm',
|
|
409
|
+
name: 'overwrite',
|
|
410
|
+
message: `⚠️ ${file.src} ya existe. ¿Deseas actualizarlo?`,
|
|
411
|
+
default: false
|
|
412
|
+
}]);
|
|
413
|
+
shouldCopy = answer.overwrite;
|
|
414
|
+
if (shouldCopy) console.log(` ${chalk.yellow('✎')} ${file.src} (Actualizado)`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (shouldCopy) {
|
|
419
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
420
|
+
// Inyectar versión dinámica si el archivo usa template
|
|
421
|
+
if (file.versionTemplate) {
|
|
422
|
+
content = content.replace(/\{\{VERSION\}\}/g, PKG_VERSION);
|
|
423
|
+
}
|
|
424
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function runInstall(options) {
|
|
431
|
+
console.clear();
|
|
432
|
+
const branding = figlet.textSync('LMAGENT', { font: 'ANSI Shadow' });
|
|
433
|
+
console.log(gradient.pastel.multiline(branding));
|
|
434
|
+
console.log(gradient.cristal(' by QuBit\n'));
|
|
435
|
+
|
|
436
|
+
const projectRoot = process.cwd();
|
|
437
|
+
|
|
438
|
+
await deployCorePillars(options, projectRoot);
|
|
439
|
+
const SOURCE_SKILLS = PACKAGE_SKILLS_DIR;
|
|
440
|
+
const SOURCE_RULES = PACKAGE_RULES_DIR;
|
|
441
|
+
const SOURCE_WORKFLOWS = PACKAGE_WORKFLOWS_DIR;
|
|
442
|
+
const SOURCE_MEMORY = PACKAGE_MEMORY_DIR;
|
|
443
|
+
|
|
444
|
+
let targetIdes = [];
|
|
445
|
+
let selectedSkills = [];
|
|
446
|
+
let selectedRules = [];
|
|
447
|
+
let selectedWorkflows = []; // New
|
|
448
|
+
let installMethod = 'symlink';
|
|
449
|
+
let installTarget = 'project';
|
|
450
|
+
let targetRoot = projectRoot;
|
|
451
|
+
|
|
452
|
+
if (options.yes) {
|
|
453
|
+
console.log(chalk.yellow('⚡ Modo: No interactivo'));
|
|
454
|
+
targetIdes = IDE_CONFIGS.filter(ide =>
|
|
455
|
+
ide.value !== 'custom' && (fs.existsSync(path.join(projectRoot, ide.rulesDir ? ide.rulesDir.split('/')[0] : '')) || (ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile))))
|
|
456
|
+
);
|
|
457
|
+
if (targetIdes.length === 0 && options.force) {
|
|
458
|
+
targetIdes = [IDE_CONFIGS.find(i => i.value === 'cursor')];
|
|
459
|
+
}
|
|
460
|
+
selectedSkills = getAllItems(SOURCE_SKILLS, true);
|
|
461
|
+
selectedRules = getAllItems(SOURCE_RULES, false);
|
|
462
|
+
selectedWorkflows = getAllItems(SOURCE_WORKFLOWS, false);
|
|
463
|
+
} else {
|
|
464
|
+
console.log(chalk.gray('================================================================'));
|
|
465
|
+
console.log(chalk.cyan('🔹 Configuración de Instalación'));
|
|
466
|
+
console.log(chalk.gray('================================================================'));
|
|
467
|
+
|
|
468
|
+
// UX OPTIMIZATION: "Project First" & Windows Compat
|
|
469
|
+
// 1. Detect Environment
|
|
470
|
+
const isWindows = os.platform() === 'win32';
|
|
471
|
+
installMethod = isWindows ? 'copy' : 'symlink';
|
|
472
|
+
installTarget = 'project';
|
|
473
|
+
targetRoot = projectRoot;
|
|
474
|
+
|
|
475
|
+
// 2. Banner simplified
|
|
476
|
+
console.log(`📍 Destino: ${chalk.green('Proyecto Actual')}`);
|
|
477
|
+
console.log(`🔧 Método: ${chalk.green(isWindows ? 'Copy (Windows Safe)' : 'Symlink (Live Updates)')}`);
|
|
478
|
+
console.log('');
|
|
479
|
+
|
|
480
|
+
// 3. Auto-Detect IDEs — SOLO en el PROYECTO (no en HOME, para evitar falsos positivos)
|
|
481
|
+
const detectedIdes = IDE_CONFIGS.filter(ide => {
|
|
482
|
+
if (ide.value === 'custom' || ide.value === 'generic') return false;
|
|
483
|
+
|
|
484
|
+
const markerInProject = ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile));
|
|
485
|
+
const rulesDirInProject = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir));
|
|
486
|
+
const skillsDirInProject = ide.skillsDir && fs.existsSync(path.join(projectRoot, ide.skillsDir));
|
|
487
|
+
// Detectar también por configFile existente en el proyecto
|
|
488
|
+
const configInProject = ide.configFile && fs.existsSync(path.join(projectRoot, ide.configFile));
|
|
489
|
+
return markerInProject || rulesDirInProject || skillsDirInProject || configInProject;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// 4. Smart Multi-Agent Auto-Detection Setup
|
|
493
|
+
if (detectedIdes.length === 0) {
|
|
494
|
+
console.log(chalk.yellow('⚠️ No se detectaron agentes instalados en este entorno.'));
|
|
495
|
+
console.log(chalk.blue('ℹ Instalando estructura estándar para Cursor por defecto.'));
|
|
496
|
+
targetIdes = [IDE_CONFIGS.find(i => i.value === 'cursor')];
|
|
497
|
+
} else {
|
|
498
|
+
console.log(chalk.green(`\n🚀 ¡Agentes Detectados! (${detectedIdes.length})`));
|
|
499
|
+
const names = detectedIdes.map(i => i.name).join(', ');
|
|
500
|
+
console.log(chalk.cyan(` → Se aplicará integración aislada para: ${chalk.bold(names)}\n`));
|
|
501
|
+
targetIdes = detectedIdes;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const availableSkills = getAllItems(SOURCE_SKILLS, true);
|
|
505
|
+
const availableRules = getAllItems(SOURCE_RULES, false);
|
|
506
|
+
const availableWorkflows = getAllItems(SOURCE_WORKFLOWS, false);
|
|
507
|
+
// Memory logic: usually just a directory, not individual items to select, but we can check if it exists
|
|
508
|
+
const hasMemory = fs.existsSync(path.join(SOURCE_SKILLS, '../memory')); // Hacky relative check or use defined constant if available in scope
|
|
509
|
+
|
|
510
|
+
console.log('');
|
|
511
|
+
const quickInstall = await inquirer.prompt([{
|
|
512
|
+
type: 'confirm',
|
|
513
|
+
name: 'all',
|
|
514
|
+
message: '⚡ Instalación Rápida: ¿Instalar TODO (Skills, Rules, Workflows, Memory)?',
|
|
515
|
+
default: true
|
|
516
|
+
}]);
|
|
517
|
+
|
|
518
|
+
if (quickInstall.all) {
|
|
519
|
+
selectedSkills = availableSkills;
|
|
520
|
+
selectedRules = availableRules;
|
|
521
|
+
selectedWorkflows = availableWorkflows;
|
|
522
|
+
options.installMemory = true; // Flag to install memory
|
|
523
|
+
} else {
|
|
524
|
+
// Manual selection...
|
|
525
|
+
// Seleccionar Skills
|
|
526
|
+
console.log(chalk.bold('\n🔹 Skills Disponibles:'));
|
|
527
|
+
const skillsAnswer = await inquirer.prompt([
|
|
528
|
+
{
|
|
529
|
+
type: 'checkbox',
|
|
530
|
+
name: 'skills',
|
|
531
|
+
message: 'Selecciona (Espacio para elegir, Enter para confirmar):',
|
|
532
|
+
choices: availableSkills.map(s => ({ name: s, checked: true })),
|
|
533
|
+
pageSize: 15
|
|
534
|
+
}
|
|
535
|
+
]);
|
|
536
|
+
selectedSkills = skillsAnswer.skills;
|
|
537
|
+
|
|
538
|
+
// Seleccionar Rules
|
|
539
|
+
console.log(chalk.bold('\n🔹 Reglas Disponibles:'));
|
|
540
|
+
const rulesAnswer = await inquirer.prompt([
|
|
541
|
+
{
|
|
542
|
+
type: 'checkbox',
|
|
543
|
+
name: 'rules',
|
|
544
|
+
message: 'Selecciona (Espacio para elegir, Enter para confirmar):',
|
|
545
|
+
choices: availableRules.map(r => ({ name: r, checked: true })),
|
|
546
|
+
pageSize: 15
|
|
547
|
+
}
|
|
548
|
+
]);
|
|
549
|
+
selectedRules = rulesAnswer.rules;
|
|
550
|
+
|
|
551
|
+
// Seleccionar Workflows
|
|
552
|
+
console.log(chalk.bold('\n🔹 Workflows Disponibles:'));
|
|
553
|
+
const workflowsAnswer = await inquirer.prompt([
|
|
554
|
+
{
|
|
555
|
+
type: 'checkbox',
|
|
556
|
+
name: 'workflows',
|
|
557
|
+
message: 'Selecciona (Espacio para elegir, Enter para confirmar):',
|
|
558
|
+
choices: availableWorkflows.map(w => ({ name: w, checked: true })),
|
|
559
|
+
pageSize: 15
|
|
560
|
+
}
|
|
561
|
+
]);
|
|
562
|
+
selectedWorkflows = workflowsAnswer.workflows;
|
|
563
|
+
|
|
564
|
+
// Seleccionar Memory
|
|
565
|
+
console.log(chalk.bold('\n🔹 Memoria (Contexto):'));
|
|
566
|
+
const memoryAnswer = await inquirer.prompt([
|
|
567
|
+
{
|
|
568
|
+
type: 'confirm',
|
|
569
|
+
name: 'memory',
|
|
570
|
+
message: '¿Instalar estructura de Memoria (.agents/memory)?',
|
|
571
|
+
default: true
|
|
572
|
+
}
|
|
573
|
+
]);
|
|
574
|
+
options.installMemory = memoryAnswer.memory;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
console.log('');
|
|
580
|
+
const { confirm } = await inquirer.prompt([{
|
|
581
|
+
type: 'confirm',
|
|
582
|
+
name: 'confirm',
|
|
583
|
+
message: '¿Proceder con la instalación?',
|
|
584
|
+
default: true
|
|
585
|
+
}]);
|
|
586
|
+
if (!confirm) return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// --- AGGRESSIVE ROOT CLEANUP ---
|
|
590
|
+
console.log(chalk.bold('\n🧹 Ejecutando limpieza de archivos root (Aislamiento de Agentes)...'));
|
|
591
|
+
// Definimos TODOS los posibles entry points
|
|
592
|
+
const allRootFiles = [
|
|
593
|
+
'.cursorrules', '.windsurfrules', '.windsurfrules.md', '.continuerules',
|
|
594
|
+
'.goosehints', '.roorules',
|
|
595
|
+
'CLAUDE.md', 'GEMINI.md', 'openclaw.json'
|
|
596
|
+
];
|
|
597
|
+
|
|
598
|
+
// Y determinamos cuáles SÍ deben existir según los agentes seleccionados
|
|
599
|
+
const requiredRootFiles = new Set();
|
|
600
|
+
|
|
601
|
+
// AGENTS.md es siempre intocable
|
|
602
|
+
requiredRootFiles.add('AGENTS.md');
|
|
603
|
+
|
|
604
|
+
for (const ide of targetIdes) {
|
|
605
|
+
if (ide.configFile && !ide.configFile.includes('/')) {
|
|
606
|
+
requiredRootFiles.add(ide.configFile);
|
|
607
|
+
}
|
|
608
|
+
if (ide.markerFile && !ide.markerFile.includes('/')) {
|
|
609
|
+
requiredRootFiles.add(ide.markerFile);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Eliminamos todo lo que NO fue requerido
|
|
614
|
+
for (const file of allRootFiles) {
|
|
615
|
+
if (!requiredRootFiles.has(file)) {
|
|
616
|
+
const filePath = path.join(targetRoot, file);
|
|
617
|
+
if (fs.existsSync(filePath)) {
|
|
618
|
+
try {
|
|
619
|
+
fs.unlinkSync(filePath);
|
|
620
|
+
console.log(` ${chalk.green('✔')} Eliminado root config obsoleto o conflictivo: ${chalk.cyan(file)}`);
|
|
621
|
+
} catch (e) {
|
|
622
|
+
console.error(` ${chalk.red('❌')} Error al eliminar ${file}: ${e.message}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
console.log('');
|
|
629
|
+
for (const ide of targetIdes) {
|
|
630
|
+
let currentInstallMethod = installMethod;
|
|
631
|
+
if (ide.forceCopy && currentInstallMethod === 'symlink') {
|
|
632
|
+
console.log(chalk.yellow(`⚠️ ${ide.name} detectado: Forzando método 'copy' (Mejor compatibilidad)`));
|
|
633
|
+
currentInstallMethod = 'copy';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
if (selectedSkills.length > 0 && ide.skillsDir) {
|
|
638
|
+
const targetDir = path.join(targetRoot, ide.skillsDir);
|
|
639
|
+
console.log(chalk.bold(`\nInstalling Skills to ${chalk.cyan(targetDir)}:`));
|
|
640
|
+
|
|
641
|
+
try {
|
|
642
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
|
643
|
+
|
|
644
|
+
for (const skill of selectedSkills) {
|
|
645
|
+
const srcFolder = path.join(SOURCE_SKILLS, skill);
|
|
646
|
+
const destFolder = path.join(targetDir, skill);
|
|
647
|
+
|
|
648
|
+
if (fs.existsSync(srcFolder)) {
|
|
649
|
+
await applyFile(srcFolder, destFolder, currentInstallMethod);
|
|
650
|
+
console.log(` ${chalk.green('✔')} ${skill}/`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
} catch (e) {
|
|
654
|
+
console.error(chalk.red(`❌ Error installing skills for ${ide.name}: ${e.message}`));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// 4. Generate/Update Global Config File (Bootstrap)
|
|
659
|
+
let bootstrapStatus = 'SKIP';
|
|
660
|
+
if (ide.configFile) {
|
|
661
|
+
// Safety: Don't inject Markdown into JSON/YAML
|
|
662
|
+
if (ide.configFile.endsWith('.json') || ide.configFile.endsWith('.yaml') || ide.configFile.endsWith('.yml')) {
|
|
663
|
+
// console.log(chalk.gray(` ℹ Skipping bootstrap for ${ide.name} (Structured Config)`));
|
|
664
|
+
bootstrapStatus = 'SKIP';
|
|
665
|
+
} else {
|
|
666
|
+
const configPath = path.join(targetRoot, ide.configFile);
|
|
667
|
+
const relativeRulesPath = getRelLink(ide.configFile, '.agents/rules/00-master.md');
|
|
668
|
+
const relativeCatalogPath = getRelLink(ide.configFile, 'AGENTS.md');
|
|
669
|
+
const relativeContextPath = getRelLink(ide.configFile, 'CLAUDE.md');
|
|
670
|
+
|
|
671
|
+
// Usar template específico del agente si existe, si no usar contenido genérico
|
|
672
|
+
const AGENT_CONFIGS_TEMPLATE_DIR = path.join(__dirname, '.agents', 'templates', 'agent-configs');
|
|
673
|
+
const templateFile = ide.configTemplate
|
|
674
|
+
? path.join(AGENT_CONFIGS_TEMPLATE_DIR, ide.configTemplate)
|
|
675
|
+
: path.join(AGENT_CONFIGS_TEMPLATE_DIR, '_generic.md');
|
|
676
|
+
|
|
677
|
+
let content;
|
|
678
|
+
if (fs.existsSync(templateFile) && !ide.configFile.endsWith('.json')) {
|
|
679
|
+
// Usar template del archivo, inyectando VERSION
|
|
680
|
+
content = fs.readFileSync(templateFile, 'utf8')
|
|
681
|
+
.replace(/\{\{VERSION\}\}/g, PKG_VERSION)
|
|
682
|
+
.replace(/\{\{MAJOR\}\}/g, PKG_VERSION.split('.')[0])
|
|
683
|
+
.replace(/\{\{SKILLS_DIR\}\}/g, ide.skillsDir || '.agents/skills')
|
|
684
|
+
.replace(/\{\{RULES_DIR\}\}/g, ide.rulesDir || '.agents/rules')
|
|
685
|
+
.replace(/\{\{WORKFLOWS_DIR\}\}/g, ide.workflowsDir || '.agents/workflows');
|
|
686
|
+
} else if (ide.configFile.endsWith('.json') && fs.existsSync(templateFile)) {
|
|
687
|
+
// JSON: usar template y reemplazar VERSION
|
|
688
|
+
content = fs.readFileSync(templateFile, 'utf8')
|
|
689
|
+
.replace(/\{\{VERSION\}\}/g, PKG_VERSION)
|
|
690
|
+
.replace(/\{\{MAJOR\}\}/g, PKG_VERSION.split('.')[0])
|
|
691
|
+
.replace(/\{\{SKILLS_DIR\}\}/g, ide.skillsDir || '.agents/skills')
|
|
692
|
+
.replace(/\{\{RULES_DIR\}\}/g, ide.rulesDir || '.agents/rules')
|
|
693
|
+
.replace(/\{\{WORKFLOWS_DIR\}\}/g, ide.workflowsDir || '.agents/workflows');
|
|
694
|
+
} else {
|
|
695
|
+
// Fallback: contenido genérico dinámico
|
|
696
|
+
content = `
|
|
697
|
+
# 🤖 LMAgent Framework v${PKG_VERSION}
|
|
698
|
+
> Contexto Activo: Este proyecto utiliza el estándar LMAgent V${PKG_VERSION.split('.')[0]}.
|
|
699
|
+
|
|
700
|
+
## 🚨 SOURCE OF TRUTH (CEREBRO)
|
|
701
|
+
**TU CONTEXTO Y REGLAS VIVEN AQUÍ 👉 [AGENTS.md](${relativeCatalogPath})**
|
|
702
|
+
*Lee este archivo INMEDIATAMENTE para obtener tu identidad, skills y reglas operativas.*
|
|
703
|
+
|
|
704
|
+
## 🔄 ARRANQUE AUTOMÁTICO (Haz esto al iniciar cada sesión)
|
|
705
|
+
1. Lee [AGENTS.md](${relativeCatalogPath}) — Tu catálogo completo de capacidades
|
|
706
|
+
2. Lee \`.agents/rules/00-master.md\` — Reglas y protocolo de trabajo
|
|
707
|
+
3. Si existe \`.agents/memory/04-active-context.md\` — Recupera contexto previo
|
|
708
|
+
4. Clasifica la tarea (Nivel 0-4) y activa el skill apropiado
|
|
709
|
+
|
|
710
|
+
## 📁 RUTAS DE ENTORNO
|
|
711
|
+
- **Skills**: \`${ide.skillsDir || '.agents/skills'}\` (31 skills disponibles)
|
|
712
|
+
- **Rules**: \`${ide.rulesDir || '.agents/rules'}\` (11 reglas)
|
|
713
|
+
- **Workflows**: \`${ide.workflowsDir || '.agents/workflows'}\` (13 SOPs)
|
|
714
|
+
- **Memory**: \`.agents/memory/\`
|
|
715
|
+
- **Config**: \`.agents/config/\`
|
|
716
|
+
|
|
717
|
+
## ⚡ QUICK START TRIGGERS (Menu Rápido)
|
|
718
|
+
Usa estos comandos para activar un rol. Para el catálogo completo de 31 skills, consulta \`AGENTS.md\`.
|
|
719
|
+
|
|
720
|
+
| Trigger | Rol / Skill | Objetivo |
|
|
721
|
+
|:--- |:--- |:--- |
|
|
722
|
+
| \`/orch\` | **Orchestrator** | Clasificar y delegar. |
|
|
723
|
+
| \`/dev\` | **Backend** | APIs y Lógica. |
|
|
724
|
+
| \`/front\` | **Frontend** | UI/UX, React. |
|
|
725
|
+
| \`/pm\` | **Product** | PRDs y Roadmap. |
|
|
726
|
+
| \`/fix\` | **Debugger** | Análisis de bugs. |
|
|
727
|
+
| \`/arch\` | **Architect** | Diseño de sistemas. |
|
|
728
|
+
|
|
729
|
+
> **IMPORTANTE**: Para activar un skill, lee su \`SKILL.md\` completo en \`${ide.skillsDir || '.agents/skills'}/[nombre]/SKILL.md\`.
|
|
730
|
+
|
|
731
|
+
!! SYSTEM NOTE: You MUST read AGENTS.md at startup to understand the full framework. !!
|
|
732
|
+
`;
|
|
733
|
+
}
|
|
734
|
+
// If file exists, check if we need to append
|
|
735
|
+
try {
|
|
736
|
+
if (fs.existsSync(configPath)) {
|
|
737
|
+
// Check if it's a directory (Edge case: Cline legacy folders)
|
|
738
|
+
if (fs.statSync(configPath).isDirectory()) {
|
|
739
|
+
console.error(chalk.red(` ❌ Cannot bootstrap ${ide.configFile}: Is a directory.`));
|
|
740
|
+
bootstrapStatus = 'ERROR';
|
|
741
|
+
} else {
|
|
742
|
+
const existingContent = fs.readFileSync(configPath, 'utf8');
|
|
743
|
+
if (!existingContent.includes('QUICK START TRIGGERS')) {
|
|
744
|
+
fs.appendFileSync(configPath, '\n' + content);
|
|
745
|
+
bootstrapStatus = 'UPDATED';
|
|
746
|
+
} else {
|
|
747
|
+
bootstrapStatus = 'OK';
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
} else {
|
|
751
|
+
// Create parent dir if needed (for .github/copilot... etc)
|
|
752
|
+
if (!fs.existsSync(path.dirname(configPath))) fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
753
|
+
fs.writeFileSync(configPath, content);
|
|
754
|
+
bootstrapStatus = 'CREATED';
|
|
755
|
+
}
|
|
756
|
+
} catch (e) {
|
|
757
|
+
console.error(chalk.red(` ❌ Error bootstrapping ${ide.name}: ${e.message}`));
|
|
758
|
+
bootstrapStatus = 'ERROR';
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (bootstrapStatus !== 'SKIP' && bootstrapStatus !== 'OK') {
|
|
764
|
+
console.log(` ${bootstrapStatus === 'CREATED' ? chalk.green('✔') : chalk.blue('ℹ')} ${ide.name} Bootstrap: ${bootstrapStatus}`);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// 4.1 Generate Bridge Rule
|
|
768
|
+
// Si el agente NO tiene configFile, necesita un archivo puente en rulesDir para auto-invocarse.
|
|
769
|
+
// Si tampoco tiene bridgeFile definido, usamos '00-lmagent.md' como default genérico.
|
|
770
|
+
const bridgeFile = ide.bridgeFile || (ide.rulesDir && !ide.configFile ? '00-lmagent.md' : null);
|
|
771
|
+
const needsBridge = bridgeFile && !ide.configFile;
|
|
772
|
+
// Garantizar que rulesDir existe siempre (para que el agente pueda detectar la instalación)
|
|
773
|
+
if (ide.rulesDir && !fs.existsSync(path.join(targetRoot, ide.rulesDir))) {
|
|
774
|
+
fs.mkdirSync(path.join(targetRoot, ide.rulesDir), { recursive: true });
|
|
775
|
+
}
|
|
776
|
+
if (ide.rulesDir && needsBridge) {
|
|
777
|
+
const bridgePath = path.join(targetRoot, ide.rulesDir, bridgeFile);
|
|
778
|
+
const relativeBridgeToRoot = path.join(ide.rulesDir, bridgeFile);
|
|
779
|
+
// Usar entry point universal (AGENTS.md) en vez de hardcodear CLAUDE.md
|
|
780
|
+
const agentEntryPoint = ide.configFile || 'AGENTS.md';
|
|
781
|
+
const relContext = getRelLink(relativeBridgeToRoot, agentEntryPoint);
|
|
782
|
+
const relCatalog = getRelLink(relativeBridgeToRoot, 'AGENTS.md');
|
|
783
|
+
const relRules = getRelLink(relativeBridgeToRoot, '.agents/rules/00-master.md');
|
|
784
|
+
const relMemory = getRelLink(relativeBridgeToRoot, '.agents/memory/04-active-context.md');
|
|
785
|
+
|
|
786
|
+
let bridgeContent = '';
|
|
787
|
+
|
|
788
|
+
if (bridgeFile.endsWith('.mdc')) {
|
|
789
|
+
// Cursor MDC Format
|
|
790
|
+
bridgeContent = `---
|
|
791
|
+
description: LMAgent Framework Entry Point - Use this rule to understand how to interact with the project skills and rules.
|
|
792
|
+
globs: **/*
|
|
793
|
+
---
|
|
794
|
+
|
|
795
|
+
# 🤖 LMAgent Bridge Rule
|
|
796
|
+
|
|
797
|
+
Este proyecto está potenciado por **LMAgent v${PKG_VERSION}**.
|
|
798
|
+
|
|
799
|
+
## 🚨 SOURCE OF TRUTH (CEREBRO)
|
|
800
|
+
**TU CONTEXTO Y REGLAS VIVEN AQUÍ 👉 [AGENTS.md](${relCatalog})**
|
|
801
|
+
*Lee este archivo INMEDIATAMENTE para obtener tu identidad, skills y reglas operativas.*
|
|
802
|
+
|
|
803
|
+
## 🔄 ARRANQUE AUTOMÁTICO (Haz esto al iniciar)
|
|
804
|
+
1. Lee [AGENTS.md](${relCatalog}) — Tu catálogo completo de capacidades
|
|
805
|
+
2. Lee [00-master.md](${relRules}) — Reglas y protocolo de trabajo
|
|
806
|
+
3. Si existe [04-active-context.md](${relMemory}) — Léelo para recuperar contexto de la sesión anterior
|
|
807
|
+
4. Clasifica la tarea (Nivel 0-4) y activa el skill apropiado
|
|
808
|
+
|
|
809
|
+
## 📁 RUTAS DE ENTORNO
|
|
810
|
+
- **Tus Skills**: \`${ide.skillsDir || '.agents/skills'}\` (31 skills disponibles)
|
|
811
|
+
- **Tus Rules**: \`${ide.rulesDir || '.agents/rules'}\` (11 reglas)
|
|
812
|
+
- **Tus Workflows**: \`${ide.workflowsDir || '.agents/workflows'}\` (13 SOPs)
|
|
813
|
+
- **Memory**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/memory') : '.agents/memory'}\`
|
|
814
|
+
- **Config**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/config') : '.agents/config'}\`
|
|
815
|
+
|
|
816
|
+
## ⚡ QUICK START TRIGGERS (Menu Rápido)
|
|
817
|
+
Usa estos comandos para activar un rol. Para el catálogo completo de 31 skills, consulta \`AGENTS.md\`.
|
|
818
|
+
|
|
819
|
+
| Trigger | Rol / Skill | Objetivo |
|
|
820
|
+
|:--- |:--- |:--- |
|
|
821
|
+
| \`/orch\` | **Orchestrator** | Clasificar y delegar. |
|
|
822
|
+
| \`/dev\` | **Backend** | APIs y Lógica. |
|
|
823
|
+
| \`/front\` | **Frontend** | UI/UX, React. |
|
|
824
|
+
| \`/pm\` | **Product** | PRDs y Roadmap. |
|
|
825
|
+
| \`/fix\` | **Debugger** | Análisis de bugs. |
|
|
826
|
+
| \`/arch\` | **Architect** | Diseño de sistemas. |
|
|
827
|
+
|
|
828
|
+
> **IMPORTANTE**: Para activar un skill, lee su \`SKILL.md\` completo en \`${ide.skillsDir || '.agents/skills'}/[nombre]/SKILL.md\`.
|
|
829
|
+
|
|
830
|
+
!! SYSTEM NOTE: You MUST read AGENTS.md at startup to understand the full framework. !!
|
|
831
|
+
`;
|
|
832
|
+
} else {
|
|
833
|
+
// Standard Markdown (Universal & Cline/Windsurf)
|
|
834
|
+
bridgeContent = `# 🤖 LMAgent Framework Entry Point
|
|
835
|
+
|
|
836
|
+
Este proyecto utiliza **LMAgent v${PKG_VERSION}**.
|
|
837
|
+
|
|
838
|
+
## 🚨 SOURCE OF TRUTH (CEREBRO)
|
|
839
|
+
**TU CONTEXTO Y REGLAS VIVEN AQUÍ 👉 [AGENTS.md](${relCatalog})**
|
|
840
|
+
*Lee este archivo INMEDIATAMENTE para obtener tu identidad, skills y reglas operativas.*
|
|
841
|
+
|
|
842
|
+
## 🔄 ARRANQUE AUTOMÁTICO (Haz esto al iniciar)
|
|
843
|
+
1. Lee [AGENTS.md](${relCatalog}) — Tu catálogo completo de capacidades
|
|
844
|
+
2. Lee [00-master.md](${relRules}) — Reglas y protocolo de trabajo
|
|
845
|
+
3. Si existe [04-active-context.md](${relMemory}) — Léelo para recuperar contexto de la sesión anterior
|
|
846
|
+
4. Clasifica la tarea (Nivel 0-4) y activa el skill apropiado
|
|
847
|
+
|
|
848
|
+
## 📁 RUTAS DE ENTORNO
|
|
849
|
+
- **Tus Skills**: \`${ide.skillsDir || '.agents/skills'}\` (31 skills disponibles)
|
|
850
|
+
- **Tus Rules**: \`${ide.rulesDir || '.agents/rules'}\` (11 reglas)
|
|
851
|
+
- **Tus Workflows**: \`${ide.workflowsDir || '.agents/workflows'}\` (13 SOPs)
|
|
852
|
+
- **Memory**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/memory') : '.agents/memory'}\`
|
|
853
|
+
- **Config**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/config') : '.agents/config'}\`
|
|
854
|
+
|
|
855
|
+
## ⚡ QUICK START TRIGGERS (Menu Rápido)
|
|
856
|
+
Usa estos comandos para activar un rol. Para el catálogo completo de 31 skills, consulta \`AGENTS.md\`.
|
|
857
|
+
|
|
858
|
+
| Trigger | Rol / Skill | Objetivo |
|
|
859
|
+
|:--- |:--- |:--- |
|
|
860
|
+
| \`/orch\` | **Orchestrator** | Clasificar y delegar. |
|
|
861
|
+
| \`/dev\` | **Backend** | APIs y Lógica. |
|
|
862
|
+
| \`/front\` | **Frontend** | UI/UX, React. |
|
|
863
|
+
| \`/pm\` | **Product** | PRDs y Roadmap. |
|
|
864
|
+
| \`/fix\` | **Debugger** | Análisis de bugs. |
|
|
865
|
+
| \`/arch\` | **Architect** | Diseño de sistemas. |
|
|
866
|
+
|
|
867
|
+
> **IMPORTANTE**: Para activar un skill, lee su \`SKILL.md\` completo en \`${ide.skillsDir || '.agents/skills'}/[nombre]/SKILL.md\`.
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// CLEANUP: Legacy Cursor Skills Directory
|
|
872
|
+
// Since we moved skills to .cursor/rules/skills, we must remove .cursor/skills to avoid duplicates
|
|
873
|
+
if (ide.value === 'cursor') {
|
|
874
|
+
const legacySkillsDir = path.join(targetRoot, '.cursor/skills');
|
|
875
|
+
if (fs.existsSync(legacySkillsDir)) {
|
|
876
|
+
try {
|
|
877
|
+
fs.rmSync(legacySkillsDir, { recursive: true, force: true });
|
|
878
|
+
console.log(` ${chalk.yellow('🗑 Eliminado directorio obsoleto:')} .cursor / skills(Movido a.cursor / rules / skills)`);
|
|
879
|
+
} catch (e) {
|
|
880
|
+
console.error(chalk.red(` ⚠️ No se pudo eliminar.cursor / skills: ${e.message} `));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
try {
|
|
886
|
+
if (!fs.existsSync(path.dirname(bridgePath))) fs.mkdirSync(path.dirname(bridgePath), { recursive: true });
|
|
887
|
+
fs.writeFileSync(bridgePath, bridgeContent);
|
|
888
|
+
console.log(` ${chalk.green('✔')} ${ide.name} Bridge Rule: ${bridgeFile} `);
|
|
889
|
+
} catch (e) {
|
|
890
|
+
console.error(chalk.red(` ❌ Error creating bridge for ${ide.name}: ${e.message} `));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
// 2. Install RULES (Files)
|
|
894
|
+
if (selectedRules.length > 0 && ide.rulesDir) {
|
|
895
|
+
const targetDir = path.join(targetRoot, ide.rulesDir);
|
|
896
|
+
console.log(chalk.bold(`\nInstalling Rules to ${chalk.cyan(targetDir)}: `));
|
|
897
|
+
|
|
898
|
+
try {
|
|
899
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
|
900
|
+
|
|
901
|
+
// CLEANUP: Remove legacy rules (V2 & Duplicates)
|
|
902
|
+
const legacyRules = [
|
|
903
|
+
'_bootstrap.md', '_bootstrap.mdc', '00-bootstrap.md',
|
|
904
|
+
'agents-ia.md', 'stack.md', 'testing.md', 'security.md', 'code-style.md', 'documentation.md',
|
|
905
|
+
'workflow.md', 'api-design.md', 'automations-n8n.md', 'frontend.md', 'backend.md'
|
|
906
|
+
];
|
|
907
|
+
for (const legacy of legacyRules) {
|
|
908
|
+
const legacyPath = path.join(targetDir, legacy);
|
|
909
|
+
if (fs.existsSync(legacyPath)) {
|
|
910
|
+
fs.unlinkSync(legacyPath);
|
|
911
|
+
console.log(` ${chalk.yellow('🗑 Eliminado regla obsoleta:')} ${legacy} `);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
for (const rule of selectedRules) {
|
|
916
|
+
const srcVal = path.join(SOURCE_RULES, rule);
|
|
917
|
+
const destVal = path.join(targetDir, rule);
|
|
918
|
+
|
|
919
|
+
if (fs.existsSync(srcVal)) {
|
|
920
|
+
await applyFile(srcVal, destVal, currentInstallMethod);
|
|
921
|
+
console.log(` ${chalk.blue('✔')} ${rule} `);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
} catch (e) {
|
|
925
|
+
console.error(chalk.red(`❌ Error installing rules for ${ide.name}: ${e.message} `));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// 3. Install WORKFLOWS (Files)
|
|
930
|
+
if (selectedWorkflows.length > 0 && ide.workflowsDir) {
|
|
931
|
+
const targetDir = path.join(targetRoot, ide.workflowsDir);
|
|
932
|
+
console.log(chalk.bold(`\nInstalling Workflows to ${chalk.cyan(targetDir)}: `));
|
|
933
|
+
|
|
934
|
+
try {
|
|
935
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
|
936
|
+
|
|
937
|
+
for (const wf of selectedWorkflows) {
|
|
938
|
+
const srcVal = path.join(SOURCE_WORKFLOWS, wf);
|
|
939
|
+
const destVal = path.join(targetDir, wf);
|
|
940
|
+
|
|
941
|
+
if (fs.existsSync(srcVal)) {
|
|
942
|
+
await applyFile(srcVal, destVal, currentInstallMethod);
|
|
943
|
+
console.log(` ${chalk.magenta('✔')} ${wf} `);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
} catch (e) {
|
|
947
|
+
console.error(chalk.red(`❌ Error installing workflows for ${ide.name}: ${e.message} `));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
if (SOURCE_MEMORY && ide.skillsDir) {
|
|
954
|
+
const parentDir = path.dirname(ide.skillsDir);
|
|
955
|
+
const targetDir = path.join(targetRoot, parentDir, 'memory');
|
|
956
|
+
const targetDirLower = targetDir.toLowerCase();
|
|
957
|
+
const sourceMemoryLower = SOURCE_MEMORY.toLowerCase();
|
|
958
|
+
|
|
959
|
+
if (targetDirLower !== sourceMemoryLower) {
|
|
960
|
+
try {
|
|
961
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
|
962
|
+
copyRecursiveSync(SOURCE_MEMORY, targetDir, true);
|
|
963
|
+
console.log(` ${chalk.cyan('✔')} Memory(Context) optimized.`);
|
|
964
|
+
} catch (e) {
|
|
965
|
+
console.error(chalk.red(`❌ Error installing memory for ${ide.name}: ${e.message} `));
|
|
966
|
+
}
|
|
967
|
+
} else {
|
|
968
|
+
console.log(` ${chalk.cyan('ℹ')} Memory(Context) already in origin path.`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// 🔄 Sincronizar Catálogo de Skills en AGENTS.md y 00-master.md
|
|
974
|
+
await syncSkillCatalog(targetRoot);
|
|
975
|
+
|
|
976
|
+
console.log(gradient.pastel.multiline('\n✨ Instalación Finalizada ✨'));
|
|
977
|
+
|
|
978
|
+
console.log(chalk.gray('================================================================'));
|
|
979
|
+
console.log(chalk.bold.green('🎉 ¡Todo listo! Aquí tienes cómo usar tus nuevos superpoderes:'));
|
|
980
|
+
console.log('');
|
|
981
|
+
|
|
982
|
+
// Mensaje dinámico según agentes instalados
|
|
983
|
+
const ideNames = targetIdes.map(i => i.name).join(', ');
|
|
984
|
+
console.log(chalk.cyan(`🤖 Agentes configurados: ${chalk.bold(ideNames)} `));
|
|
985
|
+
console.log('');
|
|
986
|
+
console.log(chalk.white(' 1. Abre tu agente en este proyecto — leerá el contexto automáticamente.'));
|
|
987
|
+
console.log(chalk.white(' 2. Usa los triggers para activar un rol específico:'));
|
|
988
|
+
console.log(chalk.gray(' /dev → Backend | /front → Frontend | /arch → Arquitecto'));
|
|
989
|
+
console.log(chalk.gray(' /fix → Debugger | /pm → Product | /orch → Orchestrator'));
|
|
990
|
+
console.log('');
|
|
991
|
+
console.log(chalk.dim(' 💡 Ejecuta `lmagent doctor` para verificar la instalación.'));
|
|
992
|
+
console.log(chalk.dim(' 💡 Ejecuta `lmagent tokens` para ver el consumo de tokens del framework.'));
|
|
993
|
+
console.log(chalk.gray('================================================================'));
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// ─── Sincronización Dinámica del Catálogo de Skills ───────────────────────────
|
|
997
|
+
// Escanea .agents/skills/*/SKILL.md, extrae frontmatter y regenera las tablas
|
|
998
|
+
// entre <!-- SKILLS_CATALOG_START --> y <!-- SKILLS_CATALOG_END --> en:
|
|
999
|
+
// - AGENTS.md (tabla de skills con trigger, nombre y directorio)
|
|
1000
|
+
// - .agents/rules/00-master.md (tabla de skills con trigger y descripción)
|
|
1001
|
+
async function syncSkillCatalog(projectRoot) {
|
|
1002
|
+
const skillsDir = path.join(projectRoot, '.agents', 'skills');
|
|
1003
|
+
if (!fs.existsSync(skillsDir)) return;
|
|
1004
|
+
|
|
1005
|
+
// 1. Escanear todos los SKILL.md y extraer frontmatter
|
|
1006
|
+
const skills = [];
|
|
1007
|
+
const skillDirs = fs.readdirSync(skillsDir).filter(d => {
|
|
1008
|
+
const p = path.join(skillsDir, d);
|
|
1009
|
+
return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md'));
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
for (const dir of skillDirs) {
|
|
1013
|
+
const skillMdPath = path.join(skillsDir, dir, 'SKILL.md');
|
|
1014
|
+
try {
|
|
1015
|
+
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
1016
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1017
|
+
if (!fmMatch) continue;
|
|
1018
|
+
|
|
1019
|
+
const fm = fmMatch[1];
|
|
1020
|
+
const name = (fm.match(/^name:\s*(.+)$/m) || [])[1]?.trim() || dir;
|
|
1021
|
+
const description = (fm.match(/^description:\s*(.+)$/m) || [])[1]?.trim() || '';
|
|
1022
|
+
|
|
1023
|
+
// Extract triggers array from YAML
|
|
1024
|
+
const triggersMatch = fm.match(/triggers:\s*\n((?:\s+-\s*.+\n?)+)/);
|
|
1025
|
+
let triggers = [];
|
|
1026
|
+
if (triggersMatch) {
|
|
1027
|
+
triggers = triggersMatch[1].match(/\s+-\s*(.+)/g)?.map(t => t.replace(/^\s+-\s*/, '').trim().replace(/["']/g, '')) || [];
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
skills.push({ slug: dir, name, description, triggers });
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
// Silently skip malformed skills
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (skills.length === 0) return;
|
|
1037
|
+
|
|
1038
|
+
console.log(chalk.bold(`\n📚 Sincronizando Catálogo de Skills (${skills.length} skills detectados)...`));
|
|
1039
|
+
|
|
1040
|
+
// 2. Generar tabla para AGENTS.md (formato: Trigger | Skill | Directorio)
|
|
1041
|
+
const agentsTableLines = skills.map(s => {
|
|
1042
|
+
const trigger = s.triggers.length > 0 ? `\`${s.triggers[0]}\`` : `\`/${s.slug.split('-')[0]}\``;
|
|
1043
|
+
return `| ${trigger} | **${s.slug}** | \`.agents/skills/${s.slug}/\` |`;
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
const agentsTableContent = `| Trigger | Skill | Directorio |
|
|
1047
|
+
|:---|:---|:---|
|
|
1048
|
+
${agentsTableLines.join('\n')}`;
|
|
1049
|
+
|
|
1050
|
+
// 3. Generar tabla para 00-master.md (formato: Skill | Triggers | Descripción)
|
|
1051
|
+
const masterTableLines = skills.map(s => {
|
|
1052
|
+
const triggerStr = s.triggers.length > 0
|
|
1053
|
+
? s.triggers.map(t => `\`${t}\``).join(', ')
|
|
1054
|
+
: `\`/${s.slug.split('-')[0]}\``;
|
|
1055
|
+
return `| **${s.slug}** | ${triggerStr} | ${s.description} |`;
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
const masterTableContent = `| Skill | Triggers | Descripción |
|
|
1059
|
+
|-------|----------|-------------|
|
|
1060
|
+
${masterTableLines.join('\n')}`;
|
|
1061
|
+
|
|
1062
|
+
// 4. Inyectar en AGENTS.md
|
|
1063
|
+
const agentsPath = path.join(projectRoot, 'AGENTS.md');
|
|
1064
|
+
if (fs.existsSync(agentsPath)) {
|
|
1065
|
+
let agentsContent = fs.readFileSync(agentsPath, 'utf8');
|
|
1066
|
+
const agentsRegex = /<!-- SKILLS_CATALOG_START -->[\s\S]*?<!-- SKILLS_CATALOG_END -->/;
|
|
1067
|
+
if (agentsRegex.test(agentsContent)) {
|
|
1068
|
+
const newSection = `<!-- SKILLS_CATALOG_START -->\n${agentsTableContent}\n<!-- SKILLS_CATALOG_END -->`;
|
|
1069
|
+
agentsContent = agentsContent.replace(agentsRegex, newSection);
|
|
1070
|
+
fs.writeFileSync(agentsPath, agentsContent, 'utf8');
|
|
1071
|
+
console.log(` ${chalk.green('✔')} AGENTS.md — Catálogo actualizado (${skills.length} skills)`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// 5. Inyectar en 00-master.md (buscar en project's .agents/rules/)
|
|
1076
|
+
const masterPath = path.join(projectRoot, '.agents', 'rules', '00-master.md');
|
|
1077
|
+
if (fs.existsSync(masterPath)) {
|
|
1078
|
+
let masterContent = fs.readFileSync(masterPath, 'utf8');
|
|
1079
|
+
const masterRegex = /<!-- SKILLS_CATALOG_START -->[\s\S]*?<!-- SKILLS_CATALOG_END -->/;
|
|
1080
|
+
if (masterRegex.test(masterContent)) {
|
|
1081
|
+
const newSection = `<!-- SKILLS_CATALOG_START -->\n${masterTableContent}\n<!-- SKILLS_CATALOG_END -->`;
|
|
1082
|
+
masterContent = masterContent.replace(masterRegex, newSection);
|
|
1083
|
+
fs.writeFileSync(masterPath, masterContent, 'utf8');
|
|
1084
|
+
console.log(` ${chalk.green('✔')} 00-master.md — Catálogo actualizado (${skills.length} skills)`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
async function applyFile(source, dest, method) {
|
|
1090
|
+
const srcPath = path.resolve(source);
|
|
1091
|
+
const destPath = path.resolve(dest);
|
|
1092
|
+
|
|
1093
|
+
// Case-insensitive check for Windows compatibility
|
|
1094
|
+
if (srcPath.toLowerCase() === destPath.toLowerCase()) {
|
|
1095
|
+
// console.log(chalk.gray(` (Skipping self - install: ${ path.basename(source) })`));
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
try {
|
|
1100
|
+
if (fs.existsSync(dest) || (fs.existsSync(path.dirname(dest)) && fs.readdirSync(path.dirname(dest)).includes(path.basename(dest)))) {
|
|
1101
|
+
const lstat = fs.lstatSync(dest);
|
|
1102
|
+
if (lstat.isSymbolicLink()) {
|
|
1103
|
+
fs.unlinkSync(dest); // Safe to remove old symlink
|
|
1104
|
+
} else if (lstat.isDirectory()) {
|
|
1105
|
+
// AUGMENTATION: No borramos el directorio real para no perder archivos del usuario.
|
|
1106
|
+
// Si el usuario pidió symlink pero hay un directorio real, forzamos merge por copia.
|
|
1107
|
+
if (method === 'symlink') method = 'copy';
|
|
1108
|
+
} else {
|
|
1109
|
+
fs.unlinkSync(dest); // It is a file, overwrite it
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
} catch (e) { }
|
|
1113
|
+
|
|
1114
|
+
const destDir = path.dirname(dest);
|
|
1115
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
1116
|
+
|
|
1117
|
+
const srcStat = fs.statSync(source);
|
|
1118
|
+
const isDir = srcStat.isDirectory();
|
|
1119
|
+
|
|
1120
|
+
if (method === 'symlink') {
|
|
1121
|
+
try {
|
|
1122
|
+
const type = isDir ? 'junction' : 'file';
|
|
1123
|
+
fs.symlinkSync(source, dest, type);
|
|
1124
|
+
} catch (e) {
|
|
1125
|
+
try {
|
|
1126
|
+
if (isDir) {
|
|
1127
|
+
copyRecursiveSync(source, dest, true);
|
|
1128
|
+
} else {
|
|
1129
|
+
fs.copyFileSync(source, dest);
|
|
1130
|
+
}
|
|
1131
|
+
const isWindows = os.platform() === 'win32';
|
|
1132
|
+
const msg = isWindows && !isDir
|
|
1133
|
+
? `(Symlink falló[Requiere Admin / DevMode en Win].Copiado.)`
|
|
1134
|
+
: `(Symlink falló, se usó copia)`;
|
|
1135
|
+
console.log(chalk.yellow(` ${msg} `));
|
|
1136
|
+
} catch (err) {
|
|
1137
|
+
console.error(chalk.red(` Error copiando ${path.basename(dest)}: ${err.message} `));
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
} else {
|
|
1141
|
+
if (isDir) {
|
|
1142
|
+
copyRecursiveSync(source, dest, true);
|
|
1143
|
+
} else {
|
|
1144
|
+
fs.copyFileSync(source, dest);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function copyRecursiveSync(src, dest, overwrite) {
|
|
1150
|
+
if (fs.cpSync) {
|
|
1151
|
+
try {
|
|
1152
|
+
fs.cpSync(src, dest, { recursive: true, force: overwrite, errorOnExist: false });
|
|
1153
|
+
} catch (e) {
|
|
1154
|
+
console.error(chalk.red(`Error copying(cpSync) ${path.basename(src)}: ${e.message} `));
|
|
1155
|
+
// Fallback manual implementation just in case
|
|
1156
|
+
if (fs.existsSync(src)) {
|
|
1157
|
+
const stat = fs.statSync(src);
|
|
1158
|
+
if (stat.isDirectory()) {
|
|
1159
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
1160
|
+
fs.readdirSync(src).forEach(child => {
|
|
1161
|
+
copyRecursiveSync(path.join(src, child), path.join(dest, child), overwrite);
|
|
1162
|
+
});
|
|
1163
|
+
} else {
|
|
1164
|
+
fs.copyFileSync(src, dest);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
} else {
|
|
1169
|
+
// Fallback for older Node versions
|
|
1170
|
+
const exists = fs.existsSync(src);
|
|
1171
|
+
const stats = exists && fs.statSync(src);
|
|
1172
|
+
const isDirectory = exists && stats.isDirectory();
|
|
1173
|
+
if (isDirectory) {
|
|
1174
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
1175
|
+
fs.readdirSync(src).forEach(function (childItemName) {
|
|
1176
|
+
copyRecursiveSync(path.join(src, childItemName),
|
|
1177
|
+
path.join(dest, childItemName), overwrite);
|
|
1178
|
+
});
|
|
1179
|
+
} else {
|
|
1180
|
+
if (overwrite || !fs.existsSync(dest)) {
|
|
1181
|
+
fs.copyFileSync(src, dest);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function getAllItems(dir, isNested) {
|
|
1188
|
+
if (!fs.existsSync(dir)) return [];
|
|
1189
|
+
const items = fs.readdirSync(dir);
|
|
1190
|
+
if (isNested) {
|
|
1191
|
+
return items.filter(item => {
|
|
1192
|
+
const p = path.join(dir, item);
|
|
1193
|
+
return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md'));
|
|
1194
|
+
});
|
|
1195
|
+
} else {
|
|
1196
|
+
return items.filter(item => item.endsWith('.md') || item.endsWith('.txt') || item.endsWith('.cursorrules') || item.endsWith('.toml'));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ============================================
|
|
1201
|
+
// INIT: Inicializar proyecto con LMAgent
|
|
1202
|
+
// ============================================
|
|
1203
|
+
|
|
1204
|
+
async function runInit(options) {
|
|
1205
|
+
let targetIdes = []; // Initialize targetIdes
|
|
1206
|
+
console.clear();
|
|
1207
|
+
const branding = figlet.textSync('LMAGENT', { font: 'ANSI Shadow' });
|
|
1208
|
+
console.log(gradient.pastel.multiline(branding));
|
|
1209
|
+
console.log(gradient.cristal(' by QuBit\n'));
|
|
1210
|
+
|
|
1211
|
+
const projectRoot = process.cwd();
|
|
1212
|
+
const targetRoot = projectRoot; // Fix for ReferenceError in runInit
|
|
1213
|
+
console.log(chalk.cyan(`📦 Inicializando proyecto LMAgent en: ${chalk.bold(projectRoot)} \n`));
|
|
1214
|
+
|
|
1215
|
+
// Verificar si ya está inicializado
|
|
1216
|
+
const agentsExists = fs.existsSync(path.join(projectRoot, 'AGENTS.md'));
|
|
1217
|
+
if (agentsExists && !options.force) {
|
|
1218
|
+
console.log(chalk.yellow('⚠️ Este proyecto ya tiene AGENTS.md'));
|
|
1219
|
+
if (!options.yes) {
|
|
1220
|
+
const { overwrite } = await inquirer.prompt([{
|
|
1221
|
+
type: 'confirm',
|
|
1222
|
+
name: 'overwrite',
|
|
1223
|
+
message: '¿Sobrescribir archivos existentes?',
|
|
1224
|
+
default: false
|
|
1225
|
+
}]);
|
|
1226
|
+
if (!overwrite) {
|
|
1227
|
+
console.log(chalk.yellow('Cancelado. Usa --force para forzar.'));
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
let filesToCopy = [...INIT_FILES];
|
|
1234
|
+
let dirsToCopy = [...INIT_DIRS];
|
|
1235
|
+
|
|
1236
|
+
// Modo interactivo: preguntar qué copiar
|
|
1237
|
+
if (!options.yes) {
|
|
1238
|
+
const answers = await inquirer.prompt([
|
|
1239
|
+
{
|
|
1240
|
+
type: 'checkbox',
|
|
1241
|
+
name: 'files',
|
|
1242
|
+
message: 'Archivos de entry point a copiar:',
|
|
1243
|
+
choices: INIT_FILES.map(f => ({
|
|
1244
|
+
name: `${f.src} - ${f.desc} `,
|
|
1245
|
+
value: f.src,
|
|
1246
|
+
checked: true
|
|
1247
|
+
}))
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
type: 'checkbox',
|
|
1251
|
+
name: 'dirs',
|
|
1252
|
+
message: 'Directorios a copiar:',
|
|
1253
|
+
choices: INIT_DIRS.map(d => ({
|
|
1254
|
+
name: `${d.src}/ - ${d.desc}`,
|
|
1255
|
+
value: d.src,
|
|
1256
|
+
checked: true
|
|
1257
|
+
}))
|
|
1258
|
+
}
|
|
1259
|
+
]);
|
|
1260
|
+
dirsToCopy = INIT_DIRS.filter(d => answers.dirs.includes(d.src));
|
|
1261
|
+
|
|
1262
|
+
// Seleccionar IDE para destino de archivos
|
|
1263
|
+
console.log('');
|
|
1264
|
+
const ideAnswer = await inquirer.prompt([
|
|
1265
|
+
{
|
|
1266
|
+
type: 'checkbox',
|
|
1267
|
+
name: 'ides',
|
|
1268
|
+
message: 'Selecciona tu IDE principal (para ubicar las carpetas):',
|
|
1269
|
+
choices: IDE_CONFIGS.filter(i => i.value !== 'custom').map(i => ({
|
|
1270
|
+
name: i.name,
|
|
1271
|
+
value: i.value,
|
|
1272
|
+
checked: i.value === 'cursor'
|
|
1273
|
+
}))
|
|
1274
|
+
}
|
|
1275
|
+
]);
|
|
1276
|
+
targetIdes = IDE_CONFIGS.filter(i => ideAnswer.ides.includes(i.value));
|
|
1277
|
+
} else {
|
|
1278
|
+
// Defaults for non-interactive
|
|
1279
|
+
targetIdes = [IDE_CONFIGS.find(i => i.value === 'cursor')];
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// Copiar archivos del framework a la carpeta del Agente (Clean Root)
|
|
1283
|
+
console.log(chalk.bold('\n📦 Instalando framework en directorios de Agente:'));
|
|
1284
|
+
|
|
1285
|
+
for (const ide of targetIdes) {
|
|
1286
|
+
if (!ide.skillsDir) continue; // Skip custom/manual if no dir defined
|
|
1287
|
+
|
|
1288
|
+
// Determinar "Agent Root" (ej: .cursor/ o .github/)
|
|
1289
|
+
// Asume que skillsDir es "root/skills", así que dirname obtiene "root"
|
|
1290
|
+
const agentRootDir = path.join(targetRoot, path.dirname(ide.skillsDir));
|
|
1291
|
+
|
|
1292
|
+
console.log(chalk.dim(` Destino: ${agentRootDir}`));
|
|
1293
|
+
|
|
1294
|
+
// Crear directorio root si no existe
|
|
1295
|
+
if (!fs.existsSync(agentRootDir)) fs.mkdirSync(agentRootDir, { recursive: true });
|
|
1296
|
+
|
|
1297
|
+
// 5. Install Root Configs (CLAUDE.md, AGENTS.md) with Prompt
|
|
1298
|
+
console.log(chalk.bold('\nChecking Root Configurations:'));
|
|
1299
|
+
for (const file of INIT_FILES) {
|
|
1300
|
+
const srcPath = path.join(__dirname, file.src);
|
|
1301
|
+
const destPath = path.join(targetRoot, file.src);
|
|
1302
|
+
|
|
1303
|
+
if (fs.existsSync(srcPath)) {
|
|
1304
|
+
if (!fs.existsSync(destPath)) {
|
|
1305
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
1306
|
+
if (file.versionTemplate) content = content.replace(/\{\{VERSION\}\}/g, PKG_VERSION);
|
|
1307
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
1308
|
+
console.log(` ${chalk.green('✔')} ${file.src} (Created)`);
|
|
1309
|
+
} else {
|
|
1310
|
+
// Exists: Ask to overwrite (unless force/yes)
|
|
1311
|
+
let shouldOverwrite = false;
|
|
1312
|
+
if (options.force) {
|
|
1313
|
+
shouldOverwrite = true;
|
|
1314
|
+
} else if (!options.yes) {
|
|
1315
|
+
const answer = await inquirer.prompt([{
|
|
1316
|
+
type: 'confirm',
|
|
1317
|
+
name: 'overwrite',
|
|
1318
|
+
message: `⚠️ ${file.src} ya existe. ¿Sobrescribir?`,
|
|
1319
|
+
default: false
|
|
1320
|
+
}]);
|
|
1321
|
+
shouldOverwrite = answer.overwrite;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
if (shouldOverwrite) {
|
|
1325
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
1326
|
+
if (file.versionTemplate) content = content.replace(/\{\{VERSION\}\}/g, PKG_VERSION);
|
|
1327
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
1328
|
+
console.log(` ${chalk.yellow('✎')} ${file.src} (Overwritten)`);
|
|
1329
|
+
} else {
|
|
1330
|
+
console.log(` ${chalk.gray('SKIP')} ${file.src} (Kept existing)`);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// Copiar Directorios (docs, config, templates)
|
|
1337
|
+
for (const dir of dirsToCopy) {
|
|
1338
|
+
const src = path.join(__dirname, dir.src);
|
|
1339
|
+
const dest = path.join(agentRootDir, dir.src);
|
|
1340
|
+
if (fs.existsSync(src)) {
|
|
1341
|
+
copyRecursiveSync(src, dest, true); // Force overwrite
|
|
1342
|
+
console.log(` ${chalk.green('✔')} ${dir.src}/ -> ${path.dirname(ide.skillsDir)}/${dir.src}/`);
|
|
1343
|
+
|
|
1344
|
+
// CLEANUP: If docs, remove assets (legacy logo, etc.)
|
|
1345
|
+
if (dir.src === 'docs') {
|
|
1346
|
+
const assetsDir = path.join(dest, 'assets');
|
|
1347
|
+
if (fs.existsSync(assetsDir)) {
|
|
1348
|
+
try {
|
|
1349
|
+
fs.rmSync(assetsDir, { recursive: true, force: true });
|
|
1350
|
+
console.log(` ${chalk.yellow('🗑 Eliminado assets heredados (logo, etc.)')}`);
|
|
1351
|
+
} catch (e) { }
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Crear .env.example si no existe
|
|
1359
|
+
const envExampleDest = path.join(projectRoot, '.env.example');
|
|
1360
|
+
if (!fs.existsSync(envExampleDest)) {
|
|
1361
|
+
const envContent = `# ============================================
|
|
1362
|
+
# LMAgent Project - Environment Variables
|
|
1363
|
+
# ============================================
|
|
1364
|
+
# Copiar este archivo a .env y completar los valores
|
|
1365
|
+
|
|
1366
|
+
# Database
|
|
1367
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
|
1368
|
+
|
|
1369
|
+
# Redis
|
|
1370
|
+
REDIS_URL=redis://localhost:6379
|
|
1371
|
+
|
|
1372
|
+
# Security
|
|
1373
|
+
JWT_SECRET=change-this-to-a-strong-secret-minimum-32-characters
|
|
1374
|
+
|
|
1375
|
+
# LLM API Keys
|
|
1376
|
+
OPENAI_API_KEY=sk-...
|
|
1377
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
1378
|
+
GOOGLE_API_KEY=...
|
|
1379
|
+
|
|
1380
|
+
# n8n (si aplica)
|
|
1381
|
+
N8N_WEBHOOK_URL=https://n8n.yourserver.com/webhook
|
|
1382
|
+
|
|
1383
|
+
# Environment
|
|
1384
|
+
ENVIRONMENT=development
|
|
1385
|
+
DEBUG=true
|
|
1386
|
+
`;
|
|
1387
|
+
fs.writeFileSync(envExampleDest, envContent);
|
|
1388
|
+
console.log(` ${chalk.green('✔')} .env.example ${chalk.green('(nuevo)')}`);
|
|
1389
|
+
} else {
|
|
1390
|
+
console.log(` ${chalk.blue('ℹ')} .env.example ya existe, no se sobrescribe`);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Resumen
|
|
1394
|
+
console.log(gradient.pastel.multiline(`\n✨ Proyecto inicializado con LMAgent v${PKG_VERSION} ✨`));
|
|
1395
|
+
console.log('');
|
|
1396
|
+
console.log(chalk.cyan('Próximos pasos:'));
|
|
1397
|
+
console.log(` 1. ${chalk.bold('lmagent install')} - Instalar skills/rules/workflows en tu IDE`);
|
|
1398
|
+
console.log(` 2. Editar ${chalk.bold('.env.example')} → ${chalk.bold('.env')} con tus credenciales`);
|
|
1399
|
+
console.log(` 3. Leer ${chalk.bold('AGENTS.md')} para conocer las capacidades disponibles`);
|
|
1400
|
+
console.log('');
|
|
1401
|
+
|
|
1402
|
+
// Preguntar si quiere ejecutar install también
|
|
1403
|
+
if (!options.yes) {
|
|
1404
|
+
const { runInstallNow } = await inquirer.prompt([{
|
|
1405
|
+
type: 'confirm',
|
|
1406
|
+
name: 'runInstallNow',
|
|
1407
|
+
message: '¿Ejecutar lmagent install ahora para conectar al IDE?',
|
|
1408
|
+
default: true
|
|
1409
|
+
}]);
|
|
1410
|
+
if (runInstallNow) {
|
|
1411
|
+
console.log('');
|
|
1412
|
+
await runInstall(options);
|
|
1413
|
+
}
|
|
1414
|
+
} else {
|
|
1415
|
+
console.log(chalk.cyan('💡 Ejecuta `lmagent install` para conectar al IDE.\n'));
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// ============================================
|
|
1420
|
+
// DOCTOR: Verificar configuración del proyecto
|
|
1421
|
+
// ============================================
|
|
1422
|
+
|
|
1423
|
+
async function runDoctor() {
|
|
1424
|
+
console.clear();
|
|
1425
|
+
const branding = figlet.textSync('LMAGENT', { font: 'ANSI Shadow' });
|
|
1426
|
+
console.log(gradient.pastel.multiline(branding));
|
|
1427
|
+
console.log(gradient.cristal(' Doctor - Diagnóstico\n'));
|
|
1428
|
+
|
|
1429
|
+
const projectRoot = process.cwd();
|
|
1430
|
+
let issues = 0;
|
|
1431
|
+
let ok = 0;
|
|
1432
|
+
|
|
1433
|
+
console.log(chalk.bold('🔍 Verificando proyecto en: ' + chalk.cyan(projectRoot) + '\n'));
|
|
1434
|
+
|
|
1435
|
+
// 1. Archivos de entry point
|
|
1436
|
+
console.log(chalk.bold('📄 Entry Points:'));
|
|
1437
|
+
for (const file of INIT_FILES) {
|
|
1438
|
+
const exists = fs.existsSync(path.join(projectRoot, file.src));
|
|
1439
|
+
if (exists) {
|
|
1440
|
+
console.log(` ${chalk.green('✔')} ${file.src}`);
|
|
1441
|
+
ok++;
|
|
1442
|
+
} else {
|
|
1443
|
+
console.log(` ${chalk.red('✘')} ${file.src} - ${chalk.red('FALTANTE')} → ejecuta ${chalk.bold('lmagent init')}`);
|
|
1444
|
+
issues++;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// 2. Directorios de configuración
|
|
1449
|
+
console.log(chalk.bold('\n📁 Configuración:'));
|
|
1450
|
+
for (const dir of INIT_DIRS) {
|
|
1451
|
+
const exists = fs.existsSync(path.join(projectRoot, dir.src));
|
|
1452
|
+
if (exists) {
|
|
1453
|
+
console.log(` ${chalk.green('✔')} ${dir.src}/`);
|
|
1454
|
+
ok++;
|
|
1455
|
+
} else {
|
|
1456
|
+
console.log(` ${chalk.yellow('⚠')} ${dir.src}/ - Opcional, ejecuta ${chalk.bold('lmagent init')} para copiar`);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// 3. Detectar IDEs configurados
|
|
1461
|
+
console.log(chalk.bold('\n🔧 IDEs detectados:'));
|
|
1462
|
+
let ideFound = false;
|
|
1463
|
+
for (const ide of IDE_CONFIGS) {
|
|
1464
|
+
if (ide.value === 'custom') continue;
|
|
1465
|
+
const rulesExist = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir));
|
|
1466
|
+
const skillsExist = ide.skillsDir && fs.existsSync(path.join(projectRoot, ide.skillsDir));
|
|
1467
|
+
const markerExist = ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile));
|
|
1468
|
+
|
|
1469
|
+
if (rulesExist || skillsExist || markerExist) {
|
|
1470
|
+
ideFound = true;
|
|
1471
|
+
const parts = [];
|
|
1472
|
+
if (rulesExist) parts.push('rules');
|
|
1473
|
+
if (skillsExist) parts.push('skills');
|
|
1474
|
+
console.log(` ${chalk.green('✔')} ${ide.name} (${parts.join(', ')})`);
|
|
1475
|
+
ok++;
|
|
1476
|
+
|
|
1477
|
+
// Verificar que tiene los skills correctos
|
|
1478
|
+
if (skillsExist) {
|
|
1479
|
+
const installedSkills = fs.readdirSync(path.join(projectRoot, ide.skillsDir))
|
|
1480
|
+
.filter(item => fs.statSync(path.join(projectRoot, ide.skillsDir, item)).isDirectory());
|
|
1481
|
+
|
|
1482
|
+
// Calcular skills esperados dinámicamente
|
|
1483
|
+
const expectedSkillsCount = getAllItems(PACKAGE_SKILLS_DIR, true).length;
|
|
1484
|
+
const skillCount = installedSkills.length;
|
|
1485
|
+
|
|
1486
|
+
if (skillCount < expectedSkillsCount) {
|
|
1487
|
+
console.log(` ${chalk.yellow('⚠')} Solo ${skillCount}/${expectedSkillsCount} skills instalados → ejecuta ${chalk.bold('lmagent install')}`);
|
|
1488
|
+
} else {
|
|
1489
|
+
console.log(` ${chalk.green('✔')} ${skillCount} skills instalados`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if (!ideFound) {
|
|
1495
|
+
console.log(` ${chalk.red('✘')} Ningún IDE detectado → ejecuta ${chalk.bold('lmagent install')}`);
|
|
1496
|
+
issues++;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// 4. Verificar .env
|
|
1500
|
+
console.log(chalk.bold('\n🔒 Seguridad:'));
|
|
1501
|
+
const envExists = fs.existsSync(path.join(projectRoot, '.env'));
|
|
1502
|
+
const envExampleExists = fs.existsSync(path.join(projectRoot, '.env.example'));
|
|
1503
|
+
const gitignoreExists = fs.existsSync(path.join(projectRoot, '.gitignore'));
|
|
1504
|
+
|
|
1505
|
+
if (envExampleExists) {
|
|
1506
|
+
console.log(` ${chalk.green('✔')} .env.example existe`);
|
|
1507
|
+
ok++;
|
|
1508
|
+
} else {
|
|
1509
|
+
console.log(` ${chalk.yellow('⚠')} .env.example no encontrado`);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
if (envExists) {
|
|
1513
|
+
console.log(` ${chalk.green('✔')} .env existe`);
|
|
1514
|
+
ok++;
|
|
1515
|
+
} else {
|
|
1516
|
+
console.log(` ${chalk.yellow('⚠')} .env no encontrado (necesario para ejecutar)`);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
if (gitignoreExists) {
|
|
1520
|
+
const gitignore = fs.readFileSync(path.join(projectRoot, '.gitignore'), 'utf-8');
|
|
1521
|
+
if (gitignore.includes('.env')) {
|
|
1522
|
+
console.log(` ${chalk.green('✔')} .env está en .gitignore`);
|
|
1523
|
+
ok++;
|
|
1524
|
+
} else {
|
|
1525
|
+
console.log(` ${chalk.red('✘')} .env NO está en .gitignore → ${chalk.red('RIESGO DE SEGURIDAD')}`);
|
|
1526
|
+
issues++;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// Resumen
|
|
1531
|
+
console.log('');
|
|
1532
|
+
if (issues === 0) {
|
|
1533
|
+
console.log(gradient.pastel(`\n✨ Todo en orden! ${ok} verificaciones pasadas.\n`));
|
|
1534
|
+
} else {
|
|
1535
|
+
console.log(chalk.yellow(`\n⚠️ ${issues} problema(s) encontrado(s), ${ok} verificaciones OK.\n`));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// Helper: Calculate relative Markdown link
|
|
1540
|
+
function getRelLink(fromRelPath, toRelPath) {
|
|
1541
|
+
const fromDir = path.dirname(fromRelPath);
|
|
1542
|
+
let rel = path.relative(fromDir, toRelPath);
|
|
1543
|
+
if (!rel.startsWith('.')) rel = './' + rel;
|
|
1544
|
+
return rel.replace(/\\/g, '/'); // Force forward slashes for Markdown
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// Helper: Contar archivos recursivamente
|
|
1548
|
+
function getAllItemsFlat(dir) {
|
|
1549
|
+
let results = [];
|
|
1550
|
+
if (!fs.existsSync(dir)) return results;
|
|
1551
|
+
const items = fs.readdirSync(dir);
|
|
1552
|
+
for (const item of items) {
|
|
1553
|
+
const fullPath = path.join(dir, item);
|
|
1554
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
1555
|
+
results = results.concat(getAllItemsFlat(fullPath));
|
|
1556
|
+
} else {
|
|
1557
|
+
results.push(fullPath);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
return results;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
// Execute CLI only if run directly
|
|
1565
|
+
if (require.main === module) {
|
|
1566
|
+
if (process.argv.length === 2) {
|
|
1567
|
+
runInstall({});
|
|
1568
|
+
} else {
|
|
1569
|
+
program.parse(process.argv);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
module.exports = { IDE_CONFIGS };
|