elsabro 2.0.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/README.md +268 -0
- package/agents/elsabro-analyst.md +176 -0
- package/agents/elsabro-debugger.md +293 -0
- package/agents/elsabro-executor.md +477 -0
- package/agents/elsabro-orchestrator.md +426 -0
- package/agents/elsabro-planner.md +278 -0
- package/agents/elsabro-qa.md +273 -0
- package/agents/elsabro-quick-dev.md +309 -0
- package/agents/elsabro-scrum-master.md +217 -0
- package/agents/elsabro-tech-writer.md +347 -0
- package/agents/elsabro-ux-designer.md +278 -0
- package/agents/elsabro-verifier.md +295 -0
- package/agents/elsabro-yolo-dev.md +322 -0
- package/bin/install.js +497 -0
- package/commands/elsabro/add-phase.md +114 -0
- package/commands/elsabro/add-todo.md +158 -0
- package/commands/elsabro/audit-milestone.md +147 -0
- package/commands/elsabro/check-todos.md +192 -0
- package/commands/elsabro/complete-milestone.md +138 -0
- package/commands/elsabro/debug.md +153 -0
- package/commands/elsabro/discuss-phase.md +160 -0
- package/commands/elsabro/execute.md +299 -0
- package/commands/elsabro/help.md +102 -0
- package/commands/elsabro/insert-phase.md +117 -0
- package/commands/elsabro/list-phase-assumptions.md +129 -0
- package/commands/elsabro/map-codebase.md +108 -0
- package/commands/elsabro/new-milestone.md +128 -0
- package/commands/elsabro/new.md +230 -0
- package/commands/elsabro/pause-work.md +261 -0
- package/commands/elsabro/plan-milestone-gaps.md +129 -0
- package/commands/elsabro/plan.md +272 -0
- package/commands/elsabro/progress.md +187 -0
- package/commands/elsabro/quick.md +99 -0
- package/commands/elsabro/remove-phase.md +136 -0
- package/commands/elsabro/research-phase.md +174 -0
- package/commands/elsabro/resume-work.md +288 -0
- package/commands/elsabro/set-profile.md +216 -0
- package/commands/elsabro/settings.md +185 -0
- package/commands/elsabro/start.md +204 -0
- package/commands/elsabro/update.md +71 -0
- package/commands/elsabro/verify-work.md +269 -0
- package/commands/elsabro/verify.md +207 -0
- package/hooks/dist/.gitkeep +2 -0
- package/package.json +45 -0
- package/references/error-handling-instructions.md +312 -0
- package/references/source-hierarchy.md +150 -0
- package/references/token-optimization.md +225 -0
- package/skills/api-setup.md +315 -0
- package/skills/auth-setup.md +180 -0
- package/skills/database-setup.md +238 -0
- package/skills/expo-app.md +261 -0
- package/skills/nextjs-app.md +206 -0
- package/skills/payments-setup.md +421 -0
- package/skills/sentry-setup.md +295 -0
- package/templates/error-handling-config.json +138 -0
- package/templates/session-state.json +69 -0
- package/templates/starters/.gitkeep +2 -0
- package/workflows/.gitkeep +2 -0
package/bin/install.js
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const { execFileSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
// Colors
|
|
10
|
+
const cyan = '\x1b[36m';
|
|
11
|
+
const green = '\x1b[32m';
|
|
12
|
+
const yellow = '\x1b[33m';
|
|
13
|
+
const red = '\x1b[31m';
|
|
14
|
+
const dim = '\x1b[2m';
|
|
15
|
+
const bold = '\x1b[1m';
|
|
16
|
+
const reset = '\x1b[0m';
|
|
17
|
+
|
|
18
|
+
// Get version from package.json
|
|
19
|
+
const pkg = require('../package.json');
|
|
20
|
+
|
|
21
|
+
// Parse args
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
24
|
+
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
25
|
+
const hasUninstall = args.includes('--uninstall') || args.includes('-u');
|
|
26
|
+
const hasUpdate = args.includes('--update');
|
|
27
|
+
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
28
|
+
|
|
29
|
+
const banner = `
|
|
30
|
+
${cyan}███████╗██╗ ███████╗ █████╗ ██████╗ ██████╗ ██████╗
|
|
31
|
+
██╔════╝██║ ██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔═══██╗
|
|
32
|
+
█████╗ ██║ ███████╗███████║██████╔╝██████╔╝██║ ██║
|
|
33
|
+
██╔══╝ ██║ ╚════██║██╔══██║██╔══██╗██╔══██╗██║ ██║
|
|
34
|
+
███████╗███████╗███████║██║ ██║██████╔╝██║ ██║╚██████╔╝
|
|
35
|
+
╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═════╝${reset}
|
|
36
|
+
|
|
37
|
+
${bold}El Sabro${reset} ${dim}v${pkg.version}${reset}
|
|
38
|
+
Tu asistente AI para crear apps increíbles
|
|
39
|
+
Sin necesidad de saber programar.
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
// Detect package manager used
|
|
43
|
+
function detectPackageManager() {
|
|
44
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
45
|
+
if (userAgent.includes('pnpm')) return 'pnpm';
|
|
46
|
+
if (userAgent.includes('bun')) return 'bun';
|
|
47
|
+
if (userAgent.includes('yarn')) return 'yarn';
|
|
48
|
+
return 'npm';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get global directory (validates all sources)
|
|
52
|
+
function getGlobalDir(explicitDir = null) {
|
|
53
|
+
let targetDir = null;
|
|
54
|
+
|
|
55
|
+
if (explicitDir) {
|
|
56
|
+
targetDir = expandTilde(explicitDir);
|
|
57
|
+
} else if (process.env.CLAUDE_CONFIG_DIR) {
|
|
58
|
+
targetDir = expandTilde(process.env.CLAUDE_CONFIG_DIR);
|
|
59
|
+
// Validate env var path too (security: env vars can be manipulated)
|
|
60
|
+
validatePath(targetDir);
|
|
61
|
+
} else {
|
|
62
|
+
return path.join(os.homedir(), '.claude');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return targetDir;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Expand ~ to home directory
|
|
69
|
+
function expandTilde(filePath) {
|
|
70
|
+
if (filePath && filePath.startsWith('~/')) {
|
|
71
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
72
|
+
}
|
|
73
|
+
return filePath;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Validate path is safe (no path traversal)
|
|
77
|
+
function validatePath(inputPath) {
|
|
78
|
+
if (!inputPath) return true;
|
|
79
|
+
|
|
80
|
+
// Check for path traversal BEFORE normalization (catches encoded attempts)
|
|
81
|
+
if (inputPath.includes('..')) {
|
|
82
|
+
console.error(` ${red}✗${reset} Ruta inválida: no se permite '..'`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Resolve to absolute path and normalize
|
|
87
|
+
const resolved = path.resolve(inputPath);
|
|
88
|
+
const homeDir = os.homedir();
|
|
89
|
+
const cwd = process.cwd();
|
|
90
|
+
|
|
91
|
+
// Expanded blocklist for system directories
|
|
92
|
+
const suspicious = ['/etc', '/usr', '/bin', '/sbin', '/var', '/tmp', '/root', '/proc', '/sys', '/dev', '/boot'];
|
|
93
|
+
|
|
94
|
+
// If not under home or cwd, check against blocklist
|
|
95
|
+
if (!resolved.startsWith(homeDir) && !resolved.startsWith(cwd)) {
|
|
96
|
+
for (const pattern of suspicious) {
|
|
97
|
+
if (resolved === pattern || resolved.startsWith(pattern + '/')) {
|
|
98
|
+
console.error(` ${red}✗${reset} Ruta inválida: no se permite instalar en ${pattern}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Windows: check for system directories and UNC paths
|
|
105
|
+
if (process.platform === 'win32') {
|
|
106
|
+
// Block UNC paths
|
|
107
|
+
if (resolved.startsWith('\\\\') || resolved.startsWith('//')) {
|
|
108
|
+
console.error(` ${red}✗${reset} Ruta inválida: UNC paths no permitidos`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const winSuspicious = ['C:\\Windows', 'C:\\Program Files', 'C:\\System32'];
|
|
113
|
+
for (const pattern of winSuspicious) {
|
|
114
|
+
if (resolved.toLowerCase().startsWith(pattern.toLowerCase())) {
|
|
115
|
+
console.error(` ${red}✗${reset} Ruta inválida: no se permite instalar en ${pattern}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Get executable name for current platform
|
|
125
|
+
function getExecutable(name) {
|
|
126
|
+
return process.platform === 'win32' ? `${name}.cmd` : name;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Parse --config-dir argument
|
|
130
|
+
function parseConfigDirArg() {
|
|
131
|
+
const configDirIndex = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
|
|
132
|
+
if (configDirIndex !== -1) {
|
|
133
|
+
const nextArg = args[configDirIndex + 1];
|
|
134
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
135
|
+
console.error(` ${yellow}--config-dir requires a path argument${reset}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
return nextArg;
|
|
139
|
+
}
|
|
140
|
+
const configDirArg = args.find(arg => arg.startsWith('--config-dir=') || arg.startsWith('-c='));
|
|
141
|
+
if (configDirArg) {
|
|
142
|
+
const value = configDirArg.split('=')[1];
|
|
143
|
+
if (!value) {
|
|
144
|
+
console.error(` ${yellow}--config-dir requires a non-empty path${reset}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const explicitConfigDir = parseConfigDirArg();
|
|
153
|
+
if (explicitConfigDir) {
|
|
154
|
+
validatePath(explicitConfigDir);
|
|
155
|
+
}
|
|
156
|
+
const packageManager = detectPackageManager();
|
|
157
|
+
|
|
158
|
+
console.log(banner);
|
|
159
|
+
console.log(` ${dim}Package manager detectado: ${packageManager}${reset}\n`);
|
|
160
|
+
|
|
161
|
+
// Show help
|
|
162
|
+
if (hasHelp) {
|
|
163
|
+
console.log(` ${yellow}Uso:${reset}
|
|
164
|
+
|
|
165
|
+
${cyan}npm:${reset} npx elsabro [opciones]
|
|
166
|
+
${cyan}pnpm:${reset} pnpm dlx elsabro [opciones]
|
|
167
|
+
${cyan}bun:${reset} bunx elsabro [opciones]
|
|
168
|
+
|
|
169
|
+
${yellow}Opciones:${reset}
|
|
170
|
+
${cyan}-g, --global${reset} Instalar globalmente (recomendado)
|
|
171
|
+
${cyan}-l, --local${reset} Instalar solo en este proyecto
|
|
172
|
+
${cyan}-u, --uninstall${reset} Desinstalar ELSABRO
|
|
173
|
+
${cyan}--update${reset} Actualizar a la última versión
|
|
174
|
+
${cyan}-c, --config-dir <ruta>${reset} Directorio de configuración personalizado
|
|
175
|
+
${cyan}-h, --help${reset} Mostrar esta ayuda
|
|
176
|
+
|
|
177
|
+
${yellow}Ejemplos:${reset}
|
|
178
|
+
${dim}# Instalar globalmente (funciona en todos los proyectos)${reset}
|
|
179
|
+
npx elsabro --global
|
|
180
|
+
pnpm dlx elsabro --global
|
|
181
|
+
bunx elsabro --global
|
|
182
|
+
|
|
183
|
+
${dim}# Instalar solo en este proyecto${reset}
|
|
184
|
+
npx elsabro --local
|
|
185
|
+
|
|
186
|
+
${dim}# Actualizar${reset}
|
|
187
|
+
npx elsabro --update
|
|
188
|
+
|
|
189
|
+
${dim}# Desinstalar${reset}
|
|
190
|
+
npx elsabro --global --uninstall
|
|
191
|
+
|
|
192
|
+
${yellow}Después de instalar:${reset}
|
|
193
|
+
Abre Claude Code y escribe: ${cyan}/elsabro:start${reset}
|
|
194
|
+
`);
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check for newer version on npm
|
|
199
|
+
const MAX_RESPONSE_SIZE = 100 * 1024; // 100KB limit for npm response
|
|
200
|
+
|
|
201
|
+
async function checkLatestVersion() {
|
|
202
|
+
try {
|
|
203
|
+
const https = require('https');
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const req = https.get('https://registry.npmjs.org/elsabro/latest', { timeout: 5000 }, (res) => {
|
|
206
|
+
let data = '';
|
|
207
|
+
res.on('data', chunk => {
|
|
208
|
+
data += chunk;
|
|
209
|
+
// Security: prevent memory exhaustion from large response
|
|
210
|
+
if (data.length > MAX_RESPONSE_SIZE) {
|
|
211
|
+
req.destroy();
|
|
212
|
+
resolve(null);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
res.on('end', () => {
|
|
216
|
+
try {
|
|
217
|
+
const json = JSON.parse(data);
|
|
218
|
+
resolve(json.version || null);
|
|
219
|
+
} catch {
|
|
220
|
+
resolve(null);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
req.on('error', () => resolve(null));
|
|
225
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
226
|
+
});
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Handle update using execFileSync (safer than exec)
|
|
233
|
+
if (hasUpdate) {
|
|
234
|
+
console.log(` ${cyan}Verificando actualizaciones...${reset}\n`);
|
|
235
|
+
|
|
236
|
+
// Check if there's a newer version first to prevent infinite loops
|
|
237
|
+
checkLatestVersion().then((latestVersion) => {
|
|
238
|
+
if (!latestVersion) {
|
|
239
|
+
console.log(` ${yellow}⚠${reset} No se pudo verificar la última versión`);
|
|
240
|
+
console.log(` ${dim}Intentando actualizar de todas formas...${reset}\n`);
|
|
241
|
+
} else if (latestVersion === pkg.version) {
|
|
242
|
+
console.log(` ${green}✓${reset} Ya tienes la última versión (${pkg.version})\n`);
|
|
243
|
+
process.exit(0);
|
|
244
|
+
} else {
|
|
245
|
+
console.log(` ${cyan}Nueva versión disponible: ${latestVersion}${reset}`);
|
|
246
|
+
console.log(` ${dim}Versión actual: ${pkg.version}${reset}\n`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
// Use execFileSync with the package manager directly
|
|
251
|
+
// Use getExecutable for Windows compatibility
|
|
252
|
+
if (packageManager === 'pnpm') {
|
|
253
|
+
execFileSync(getExecutable('pnpm'), ['dlx', 'elsabro@latest', '--global'], { stdio: 'inherit' });
|
|
254
|
+
} else if (packageManager === 'bun') {
|
|
255
|
+
execFileSync(getExecutable('bunx'), ['elsabro@latest', '--global'], { stdio: 'inherit' });
|
|
256
|
+
} else {
|
|
257
|
+
execFileSync(getExecutable('npx'), ['elsabro@latest', '--global'], { stdio: 'inherit' });
|
|
258
|
+
}
|
|
259
|
+
console.log(`\n ${green}✓${reset} ELSABRO actualizado correctamente\n`);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error(`\n ${red}✗${reset} Error al actualizar: ${error.message}\n`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
process.exit(0);
|
|
265
|
+
});
|
|
266
|
+
} else {
|
|
267
|
+
|
|
268
|
+
// Interactive prompt for location if not specified
|
|
269
|
+
async function promptLocation() {
|
|
270
|
+
if (hasGlobal || hasLocal) return hasGlobal ? 'global' : 'local';
|
|
271
|
+
|
|
272
|
+
const rl = readline.createInterface({
|
|
273
|
+
input: process.stdin,
|
|
274
|
+
output: process.stdout
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
console.log(` ${yellow}¿Dónde quieres instalar ELSABRO?${reset}\n`);
|
|
279
|
+
console.log(` ${cyan}1)${reset} Global ${dim}(recomendado - funciona en todos los proyectos)${reset}`);
|
|
280
|
+
console.log(` ${cyan}2)${reset} Local ${dim}(solo en este proyecto)${reset}\n`);
|
|
281
|
+
|
|
282
|
+
rl.question(` Tu elección [1]: `, (answer) => {
|
|
283
|
+
rl.close();
|
|
284
|
+
resolve(answer === '2' ? 'local' : 'global');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Copy directory recursively with security limits
|
|
290
|
+
const MAX_DEPTH = 20;
|
|
291
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
292
|
+
|
|
293
|
+
function copyDir(src, dest, depth = 0) {
|
|
294
|
+
// Prevent stack overflow from deeply nested directories
|
|
295
|
+
if (depth > MAX_DEPTH) {
|
|
296
|
+
throw new Error(`Profundidad máxima excedida en: ${src}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check source is not a symlink (security: prevent symlink attacks)
|
|
300
|
+
const srcStats = fs.lstatSync(src);
|
|
301
|
+
if (srcStats.isSymbolicLink()) {
|
|
302
|
+
console.log(` ${yellow}⚠${reset} Saltando symlink: ${src}`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
307
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
308
|
+
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
const srcPath = path.join(src, entry.name);
|
|
311
|
+
const destPath = path.join(dest, entry.name);
|
|
312
|
+
|
|
313
|
+
// Check each entry for symlinks
|
|
314
|
+
const entryStats = fs.lstatSync(srcPath);
|
|
315
|
+
if (entryStats.isSymbolicLink()) {
|
|
316
|
+
console.log(` ${yellow}⚠${reset} Saltando symlink: ${entry.name}`);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (entry.isDirectory()) {
|
|
321
|
+
copyDir(srcPath, destPath, depth + 1);
|
|
322
|
+
} else {
|
|
323
|
+
// Check file size before copying
|
|
324
|
+
if (entryStats.size > MAX_FILE_SIZE) {
|
|
325
|
+
throw new Error(`Archivo demasiado grande: ${srcPath} (${Math.round(entryStats.size / 1024 / 1024)}MB)`);
|
|
326
|
+
}
|
|
327
|
+
fs.copyFileSync(srcPath, destPath);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Main install function
|
|
333
|
+
async function install() {
|
|
334
|
+
const location = await promptLocation();
|
|
335
|
+
const isGlobal = location === 'global';
|
|
336
|
+
|
|
337
|
+
const baseDir = isGlobal
|
|
338
|
+
? getGlobalDir(explicitConfigDir)
|
|
339
|
+
: path.join(process.cwd(), '.claude');
|
|
340
|
+
|
|
341
|
+
console.log(`\n ${cyan}Instalando ELSABRO...${reset}`);
|
|
342
|
+
console.log(` ${dim}Destino: ${baseDir}${reset}\n`);
|
|
343
|
+
|
|
344
|
+
// Create base directory
|
|
345
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
346
|
+
|
|
347
|
+
// Directories to copy
|
|
348
|
+
const srcDir = path.join(__dirname, '..');
|
|
349
|
+
const copies = [
|
|
350
|
+
{ src: 'commands/elsabro', dest: 'commands/elsabro', name: 'Comandos' },
|
|
351
|
+
{ src: 'agents', dest: 'agents', name: 'Agentes' },
|
|
352
|
+
{ src: 'skills', dest: 'skills', name: 'Skills' },
|
|
353
|
+
{ src: 'templates', dest: 'templates', name: 'Templates' },
|
|
354
|
+
{ src: 'workflows', dest: 'workflows', name: 'Workflows' },
|
|
355
|
+
{ src: 'references', dest: 'references', name: 'Referencias' },
|
|
356
|
+
{ src: 'hooks/dist', dest: 'hooks', name: 'Hooks' }
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
const failures = [];
|
|
360
|
+
|
|
361
|
+
for (const item of copies) {
|
|
362
|
+
const srcPath = path.join(srcDir, item.src);
|
|
363
|
+
const destPath = path.join(baseDir, item.dest);
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
// Safe removal: check for symlinks before removing
|
|
367
|
+
if (fs.existsSync(destPath)) {
|
|
368
|
+
const destStats = fs.lstatSync(destPath);
|
|
369
|
+
if (destStats.isSymbolicLink()) {
|
|
370
|
+
console.log(` ${yellow}⚠${reset} Saltando symlink existente: ${destPath}`);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
fs.rmSync(destPath, { recursive: true });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (fs.existsSync(srcPath)) {
|
|
377
|
+
copyDir(srcPath, destPath);
|
|
378
|
+
console.log(` ${green}✓${reset} ${item.name} instalados`);
|
|
379
|
+
} else {
|
|
380
|
+
console.log(` ${yellow}⚠${reset} ${item.name} no encontrados (saltando)`);
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.log(` ${red}✗${reset} Error instalando ${item.name}: ${error.message}`);
|
|
384
|
+
failures.push(item.name);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Update settings.json for hooks
|
|
389
|
+
const settingsPath = path.join(baseDir, 'settings.json');
|
|
390
|
+
let settings = {};
|
|
391
|
+
|
|
392
|
+
if (fs.existsSync(settingsPath)) {
|
|
393
|
+
try {
|
|
394
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
395
|
+
} catch (e) {
|
|
396
|
+
settings = {};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Add hook configurations
|
|
401
|
+
settings.hooks = settings.hooks || {};
|
|
402
|
+
settings.hooks.postToolUse = settings.hooks.postToolUse || [];
|
|
403
|
+
|
|
404
|
+
// Add version tracking
|
|
405
|
+
settings.elsabro = {
|
|
406
|
+
version: pkg.version,
|
|
407
|
+
installedAt: new Date().toISOString(),
|
|
408
|
+
packageManager: packageManager
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
412
|
+
console.log(` ${green}✓${reset} Configuración actualizada`);
|
|
413
|
+
|
|
414
|
+
// Summary
|
|
415
|
+
console.log(`\n ${green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}`);
|
|
416
|
+
|
|
417
|
+
if (failures.length === 0) {
|
|
418
|
+
console.log(` ${green}✓${reset} ${bold}ELSABRO instalado correctamente${reset}`);
|
|
419
|
+
console.log(`\n ${yellow}Próximos pasos:${reset}`);
|
|
420
|
+
console.log(` 1. Abre Claude Code`);
|
|
421
|
+
console.log(` 2. Escribe: ${cyan}/elsabro:start${reset}`);
|
|
422
|
+
console.log(` 3. ¡Empieza a crear tu app!\n`);
|
|
423
|
+
|
|
424
|
+
console.log(` ${dim}Comandos útiles:${reset}`);
|
|
425
|
+
console.log(` ${cyan}/elsabro:start${reset} - Wizard para empezar`);
|
|
426
|
+
console.log(` ${cyan}/elsabro:help${reset} - Ver todos los comandos`);
|
|
427
|
+
console.log(` ${cyan}/elsabro:update${reset} - Actualizar ELSABRO\n`);
|
|
428
|
+
} else {
|
|
429
|
+
console.log(` ${yellow}⚠${reset} ELSABRO instalado con algunos errores`);
|
|
430
|
+
console.log(` Componentes fallidos: ${failures.join(', ')}\n`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
console.log(` ${green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\n`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Uninstall function
|
|
437
|
+
async function uninstall() {
|
|
438
|
+
const location = await promptLocation();
|
|
439
|
+
const isGlobal = location === 'global';
|
|
440
|
+
|
|
441
|
+
const baseDir = isGlobal
|
|
442
|
+
? getGlobalDir(explicitConfigDir)
|
|
443
|
+
: path.join(process.cwd(), '.claude');
|
|
444
|
+
|
|
445
|
+
console.log(`\n ${cyan}Desinstalando ELSABRO...${reset}\n`);
|
|
446
|
+
|
|
447
|
+
const toRemove = [
|
|
448
|
+
'commands/elsabro',
|
|
449
|
+
'agents',
|
|
450
|
+
'skills',
|
|
451
|
+
'templates',
|
|
452
|
+
'workflows',
|
|
453
|
+
'references'
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
for (const item of toRemove) {
|
|
457
|
+
const itemPath = path.join(baseDir, item);
|
|
458
|
+
if (fs.existsSync(itemPath)) {
|
|
459
|
+
// Safe removal: check for symlinks before removing
|
|
460
|
+
const itemStats = fs.lstatSync(itemPath);
|
|
461
|
+
if (itemStats.isSymbolicLink()) {
|
|
462
|
+
console.log(` ${yellow}⚠${reset} Saltando symlink: ${item}`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
fs.rmSync(itemPath, { recursive: true });
|
|
466
|
+
console.log(` ${green}✓${reset} Eliminado: ${item}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Remove from settings
|
|
471
|
+
const settingsPath = path.join(baseDir, 'settings.json');
|
|
472
|
+
if (fs.existsSync(settingsPath)) {
|
|
473
|
+
try {
|
|
474
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
475
|
+
delete settings.elsabro;
|
|
476
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
477
|
+
} catch (e) {
|
|
478
|
+
// Ignore
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
console.log(`\n ${green}✓${reset} ELSABRO desinstalado correctamente\n`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Main (note: update handling is above with its own process.exit)
|
|
486
|
+
if (hasUninstall) {
|
|
487
|
+
uninstall().catch((error) => {
|
|
488
|
+
console.error(` ${red}✗${reset} Error: ${error.message}`);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
install().catch((error) => {
|
|
493
|
+
console.error(` ${red}✗${reset} Error: ${error.message}`);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-phase
|
|
3
|
+
description: Agregar una nueva fase al final del milestone actual
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /elsabro:add-phase
|
|
7
|
+
|
|
8
|
+
<command-name>add-phase</command-name>
|
|
9
|
+
|
|
10
|
+
## Propósito
|
|
11
|
+
|
|
12
|
+
Agregar una nueva fase al milestone activo. Útil cuando se descubre trabajo adicional necesario.
|
|
13
|
+
|
|
14
|
+
## Uso
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Agregar fase básica
|
|
18
|
+
/elsabro:add-phase "Documentation"
|
|
19
|
+
|
|
20
|
+
# Agregar con descripción
|
|
21
|
+
/elsabro:add-phase "Documentation" --desc="Write user guides and API docs"
|
|
22
|
+
|
|
23
|
+
# Agregar a milestone específico
|
|
24
|
+
/elsabro:add-phase "Documentation" --milestone=M001
|
|
25
|
+
|
|
26
|
+
# Agregar con estimación
|
|
27
|
+
/elsabro:add-phase "Documentation" --estimate=3d
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Proceso
|
|
31
|
+
|
|
32
|
+
### 1. Crear Fase
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
User: /elsabro:add-phase "Integration Testing"
|
|
36
|
+
|
|
37
|
+
ELSABRO: Creating new phase...
|
|
38
|
+
|
|
39
|
+
Phase added to M001:
|
|
40
|
+
┌──────────────────────────────────────┐
|
|
41
|
+
│ P5: Integration Testing │
|
|
42
|
+
│ Status: ⬜ Pending │
|
|
43
|
+
│ Estimate: TBD │
|
|
44
|
+
│ Dependencies: P4 │
|
|
45
|
+
└──────────────────────────────────────┘
|
|
46
|
+
|
|
47
|
+
Define tasks for this phase? (y/n)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Definir Tasks (Opcional)
|
|
51
|
+
|
|
52
|
+
Si responde 'y':
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Enter tasks (one per line, empty to finish):
|
|
56
|
+
> Set up integration test environment
|
|
57
|
+
> Write API integration tests
|
|
58
|
+
> Write E2E tests for critical flows
|
|
59
|
+
>
|
|
60
|
+
|
|
61
|
+
Tasks added:
|
|
62
|
+
- [ ] Set up integration test environment
|
|
63
|
+
- [ ] Write API integration tests
|
|
64
|
+
- [ ] Write E2E tests for critical flows
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Actualización Automática
|
|
68
|
+
|
|
69
|
+
Actualiza `PHASES.md` y `MILESTONE.md`:
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
## Phases
|
|
73
|
+
|
|
74
|
+
| # | Phase | Status | Tasks | ETA |
|
|
75
|
+
|---|-------|--------|-------|-----|
|
|
76
|
+
| P1 | Design | ✅ Done | 5/5 | W1 |
|
|
77
|
+
| P2 | Backend | ✅ Done | 8/8 | W2 |
|
|
78
|
+
| P3 | Frontend | 🟡 WIP | 4/6 | W3 |
|
|
79
|
+
| P4 | Testing | ⬜ Pending | 0/3 | W4 |
|
|
80
|
+
| P5 | Integration Testing | ⬜ NEW | 0/3 | W5 | ← Added
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Opciones
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
--milestone, -m # Milestone target (default: active)
|
|
87
|
+
--desc, -d # Phase description
|
|
88
|
+
--estimate, -e # Time estimate (1d, 2w, etc.)
|
|
89
|
+
--after, -a # Insert after specific phase
|
|
90
|
+
--deps # Dependencies (comma-separated)
|
|
91
|
+
--tasks # Initial tasks (comma-separated)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Validaciones
|
|
95
|
+
|
|
96
|
+
- Verifica milestone existe
|
|
97
|
+
- Verifica nombre único de fase
|
|
98
|
+
- Sugiere dependencias basado en orden
|
|
99
|
+
|
|
100
|
+
## Output
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
✓ Phase "Integration Testing" added to M001
|
|
104
|
+
|
|
105
|
+
Position: P5 (after P4: Testing)
|
|
106
|
+
Status: Pending
|
|
107
|
+
Tasks: 3 defined
|
|
108
|
+
|
|
109
|
+
Impact on milestone:
|
|
110
|
+
- Original ETA: 2024-02-15
|
|
111
|
+
- New ETA: 2024-02-22 (+1 week)
|
|
112
|
+
|
|
113
|
+
View phases: /elsabro:progress --phases
|
|
114
|
+
```
|