claude-git-hooks 1.1.0 → 1.2.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,97 @@
1
+ # Changelog
2
+
3
+ Todos los cambios notables en este proyecto se documentarán en este archivo.
4
+
5
+ El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.2.1] - 2024-07-24
9
+
10
+ ### Fixed
11
+ - 🐛 Corregido problema de bloqueo del spinner durante verificación de autenticación Claude
12
+ - 🎨 Arreglado sistema de entretenimiento para mostrar correctamente spinner, chistes y countdown
13
+ - 🔧 Solucionado problema de líneas duplicadas en consola después del segundo chiste
14
+ - 🧹 Mejorada limpieza de pantalla para evitar superposición de texto al finalizar spinner
15
+ - ⏱️ Aumentado timeout de verificación de autenticación a 2 minutos
16
+
17
+ ### Changed
18
+ - 🔄 Reemplazado `execSync` por `spawn` en verificación de autenticación para evitar bloqueos
19
+ - 📍 Mejorado sistema de posicionamiento del cursor para renderizado consistente
20
+ - 🎯 Optimizada lógica de actualización del spinner para evitar race conditions
21
+
22
+ ### Technical
23
+ - 🏗️ Uso de `spawn` con `stdio: 'ignore'` para mantener compatibilidad con comportamiento original
24
+ - 🎮 Sistema de reserva de espacio (3 líneas) para evitar problemas de renderizado
25
+ - 🧮 Contador de chistes cada 10 segundos real (sin drift de tiempo)
26
+ - 🔍 Limpieza precisa línea por línea al finalizar para evitar artefactos visuales
27
+
28
+ ## [1.2.0] - 2024-07-24
29
+
30
+ ### Added
31
+ - ✨ Sistema de entretenimiento con spinner animado y chistes durante operaciones largas
32
+ - 🔐 Lectura segura y no-persistente de contraseña sudo para instalación automática
33
+ - 🛠️ Verificación completa de dependencias del sistema (jq, curl, herramientas Unix)
34
+ - 🚀 Instalación automática de dependencias faltantes con contraseña sudo
35
+ - ⚙️ Configuración automática de Git (line endings para WSL/Windows)
36
+ - 🎭 Chistes rotativos cada 10 segundos durante verificación de autenticación Claude
37
+
38
+ ### Enhanced
39
+ - 📦 Función `install` ahora incluye verificación e instalación completa de dependencias
40
+ - 🔍 Verificación de autenticación Claude con entretenimiento visual
41
+ - 💻 Detección inteligente de plataforma (Linux/macOS) para instalación de paquetes
42
+
43
+ ### Removed
44
+ - 🗑️ Eliminados archivos `setup-wsl.sh` y `setup-wsl.js` (redundantes)
45
+ - 🧹 Simplificación de la arquitectura eliminando duplicación de código
46
+
47
+ ### Technical
48
+ - 🔧 Añadido módulo `https` para obtener chistes de API externa
49
+ - 🎨 Clase `Entertainment` para manejo de spinner y chistes
50
+ - 🔐 Funciones `readPassword()` y `testSudoPassword()` para manejo seguro de credenciales
51
+ - ⚡ Conversión a funciones async/await para mejor manejo de procesos asíncronos
52
+
53
+ ## [1.1.0] - 2024-07-24
54
+
55
+ ### Added
56
+ - 🎯 Comando `set-mode` para cambiar entre análisis estándar y SonarQube
57
+ - 📊 Modo SonarQube con métricas detalladas y quality gate
58
+ - 📈 Visualización del modo de análisis actual en comando `status`
59
+ - 🔧 Configuración persistente del modo de análisis en archivo `.claude-analysis-mode`
60
+
61
+ ### Enhanced
62
+ - 📋 Comando `status` ahora muestra modo de análisis y archivos de pautas
63
+ - 📚 Documentación mejorada con ejemplos de ambos modos
64
+ - 🎨 Mejor formateo de salida en comandos informativos
65
+
66
+ ### Technical
67
+ - 📁 Detección automática de archivos de pautas (CLAUDE_PRE_COMMIT.md, CLAUDE_PRE_COMMIT_SONAR.md)
68
+ - 🏗️ Estructura preparada para múltiples modos de análisis
69
+
70
+ ## [1.0.0] - 2024-07-24
71
+
72
+ ### Added
73
+ - 🎉 Primera versión estable del paquete npm `claude-git-hooks`
74
+ - 📦 CLI global `claude-hooks` para gestión de hooks de Git
75
+ - 🪝 Instalación automática de hooks `pre-commit` y `prepare-commit-msg`
76
+ - 🔍 Verificación de dependencias (Claude CLI, jq)
77
+ - 📋 Comandos principales: `install`, `uninstall`, `enable`, `disable`, `status`
78
+ - 📖 Sistema de ayuda completo con ejemplos
79
+ - 🎛️ Gestión individual de hooks (habilitar/deshabilitar por separado)
80
+ - 📂 Creación automática de archivos de pautas si no existen
81
+ - 🔄 Sistema de backup automático de hooks existentes
82
+
83
+ ### Technical
84
+ - 🏗️ Arquitectura modular con funciones especializadas
85
+ - 🎨 Sistema de colores para output legible
86
+ - 🛡️ Validaciones de entorno (repositorio Git, dependencias)
87
+ - 📝 Documentación completa en README
88
+
89
+ ---
90
+
91
+ ## Tipos de cambios
92
+ - `Added` - para nuevas funcionalidades
93
+ - `Changed` - para cambios en funcionalidades existentes
94
+ - `Deprecated` - para funcionalidades que serán removidas
95
+ - `Removed` - para funcionalidades removidas
96
+ - `Fixed` - para corrección de bugs
97
+ - `Security` - para vulnerabilidades corregidas
package/README.md CHANGED
@@ -7,7 +7,6 @@ Este directorio contiene un pre-commit hook que utiliza Claude CLI para revisar
7
7
  - **`README.md`** - Esta documentación
8
8
  - **`pre-commit`** - Hook principal para análisis de código (con auto-actualización)
9
9
  - **`prepare-commit-msg`** - Hook para generar mensajes de commit automáticos
10
- - **`setup-wsl.sh`** - Script de instalación inicial para WSL
11
10
  - **`.gitattributes`** - Configuración para mantener line endings correctos
12
11
  - **`CLAUDE_PRE_COMMIT.md`** - Pautas de evaluación formato estándar
13
12
  - **`CLAUDE_PRE_COMMIT_SONAR.md`** - Pautas de evaluación formato SonarQube
@@ -51,28 +50,31 @@ git config --global credential.helper store
51
50
 
52
51
  ## 🚀 Instalación
53
52
 
54
- ### Opción 1: Paquete NPM Global (Recomendado)
53
+ ### Paquete NPM Global (Recomendado)
55
54
 
56
55
  **IMPORTANTE**: Claude CLI corre en WSL, por lo que toda la instalación y uso de git debe hacerse desde la terminal WSL.
57
56
 
58
- #### Instalación Global
59
-
60
57
  ```bash
61
58
  # Instalar el paquete globalmente
62
- sudo npm install -g claude-git-hooks
59
+ npm install -g claude-git-hooks
63
60
 
64
61
  # En cualquier repositorio, instalar los hooks
65
62
  cd tu-proyecto
66
63
  claude-hooks install
67
64
  ```
68
65
 
66
+ El comando `claude-hooks install` ahora incluye:
67
+ - ✅ Verificación completa de dependencias del sistema
68
+ - ✅ Instalación automática de paquetes faltantes (jq, curl)
69
+ - ✅ Configuración de Git (line endings WSL/Windows)
70
+ - ✅ Verificación de autenticación Claude con entretenimiento
71
+ - ✅ Instalación de hooks y archivos de pautas
72
+
69
73
  #### Añadir como Dependencia de Desarrollo
70
74
 
71
75
  ```bash
72
76
  # Instalar como devDependency
73
77
  npm install --save-dev claude-git-hooks
74
-
75
- # Añadir script al package.json
76
78
  ```
77
79
 
78
80
  Luego añade esto a tu `package.json`:
@@ -83,30 +85,11 @@ Luego añade esto a tu `package.json`:
83
85
  "postinstall": "claude-hooks install"
84
86
  },
85
87
  "devDependencies": {
86
- "claude-git-hooks": "^1.0.0"
88
+ "claude-git-hooks": "^1.2.0"
87
89
  }
88
90
  }
89
91
  ```
90
92
 
91
- ### Opción 2: Instalación Local con Scripts
92
-
93
- ```bash
94
- # Desde WSL - Instalación completa
95
- ./git-hooks/setup-wsl.sh
96
-
97
- # Ver opciones disponibles
98
- ./git-hooks/setup-wsl.sh --help
99
-
100
- # Solo sincronizar hooks (útil después de modificar archivos fuente)
101
- ./git-hooks/setup-wsl.sh --sync
102
-
103
- # Habilitar/deshabilitar hooks
104
- ./git-hooks/setup-wsl.sh --enable-hooks # Habilita todos los hooks
105
- ./git-hooks/setup-wsl.sh --disable-hooks # Deshabilita todos los hooks
106
- ./git-hooks/setup-wsl.sh --enable-hooks pre-commit # Habilita solo pre-commit
107
- ./git-hooks/setup-wsl.sh --disable-hooks prepare-commit-msg # Deshabilita solo prepare-commit-msg
108
- ```
109
-
110
93
  ## 🤖 Características
111
94
 
112
95
  **✨ Auto-actualización incorporada**: Los hooks se actualizan automáticamente en cada commit.
@@ -405,16 +388,15 @@ WSL Terminal
405
388
  ```
406
389
  claude-git-hooks/
407
390
  ├── bin/
408
- │ └── claude-hooks # CLI principal
391
+ │ └── claude-hooks # CLI principal con verificación completa
409
392
  ├── templates/
410
393
  │ ├── pre-commit # Hook de análisis de código
411
394
  │ ├── prepare-commit-msg # Hook de generación de mensajes
412
395
  │ ├── CLAUDE_PRE_COMMIT.md # Pautas estándar
413
396
  │ └── CLAUDE_PRE_COMMIT_SONAR.md # Pautas SonarQube
414
- ├── setup-wsl.sh # Script de instalación local (legacy)
415
397
  ├── package.json # Configuración NPM
416
398
  ├── README.md # Este archivo
417
- ├── README-NPM.md # Documentación para NPM
399
+ ├── CHANGELOG.md # Historial de versiones
418
400
  └── PUBLISH.md # Guía de publicación
419
401
  ```
420
402
 
package/bin/claude-hooks CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync } = require('child_process');
3
+ const { execSync, spawn } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
7
+ const readline = require('readline');
8
+ const https = require('https');
7
9
 
8
10
  // Colores para output
9
11
  const colors = {
@@ -35,6 +37,206 @@ function warning(message) {
35
37
  log(`⚠️ ${message}`, 'yellow');
36
38
  }
37
39
 
40
+ // Función para leer contraseña de forma segura
41
+ function readPassword(prompt) {
42
+ return new Promise((resolve) => {
43
+ const rl = readline.createInterface({
44
+ input: process.stdin,
45
+ output: process.stdout
46
+ });
47
+
48
+ // Deshabilitar echo
49
+ rl.stdoutMuted = true;
50
+ rl._writeToOutput = function _writeToOutput(stringToWrite) {
51
+ if (rl.stdoutMuted)
52
+ rl.output.write("*");
53
+ else
54
+ rl.output.write(stringToWrite);
55
+ };
56
+
57
+ rl.question(prompt, (password) => {
58
+ rl.close();
59
+ console.log(); // Nueva línea
60
+ resolve(password);
61
+ });
62
+ });
63
+ }
64
+
65
+ // Verificar si la contraseña sudo es correcta
66
+ function testSudoPassword(password) {
67
+ try {
68
+ execSync('echo "' + password + '" | sudo -S true', {
69
+ stdio: 'ignore',
70
+ timeout: 5000
71
+ });
72
+ return true;
73
+ } catch (e) {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ // Instalar paquete con sudo automático
79
+ function installPackage(packageName, sudoPassword = null) {
80
+ try {
81
+ if (sudoPassword) {
82
+ if (os.platform() === 'linux') {
83
+ execSync(`echo "${sudoPassword}" | sudo -S apt-get update && echo "${sudoPassword}" | sudo -S apt-get install -y ${packageName}`, {
84
+ stdio: 'inherit'
85
+ });
86
+ }
87
+ } else {
88
+ if (os.platform() === 'linux') {
89
+ execSync(`sudo apt-get update && sudo apt-get install -y ${packageName}`, {
90
+ stdio: 'inherit'
91
+ });
92
+ } else if (os.platform() === 'darwin') {
93
+ execSync(`brew install ${packageName}`, {
94
+ stdio: 'inherit'
95
+ });
96
+ }
97
+ }
98
+ return true;
99
+ } catch (e) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ // Sistema de entretenimiento
105
+ class Entertainment {
106
+ static jokes = [
107
+ "¿Por qué los programadores prefieren el modo oscuro? Porque la luz atrae bugs!",
108
+ "Un QA entra a un bar. Pide 1 cerveza. Pide 0 cervezas. Pide -1 cervezas.",
109
+ "¿Cuál es el lenguaje favorito de los piratas? R!",
110
+ "Hay 10 tipos de personas: las que entienden binario y las que no.",
111
+ "¿Por qué los programadores confunden Halloween con Navidad? Porque Oct 31 = Dec 25",
112
+ "¿Qué le dice un bit al otro? Nos vemos en el bus!",
113
+ "¿Por qué Java y C++ no se llevan bien? Porque tienen diferentes puntos de vista sobre los punteros.",
114
+ "Mi código no tiene bugs, solo features no documentadas."
115
+ ];
116
+
117
+ static async getJoke() {
118
+ return new Promise((resolve) => {
119
+ // Intentar obtener chiste de API
120
+ const req = https.get('https://icanhazdadjoke.com/', {
121
+ headers: { 'Accept': 'text/plain' },
122
+ timeout: 3000
123
+ }, (res) => {
124
+ let data = '';
125
+ res.on('data', chunk => data += chunk);
126
+ res.on('end', () => resolve(data.trim()));
127
+ });
128
+
129
+ req.on('error', () => {
130
+ // Si falla, usar chiste local
131
+ const randomJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
132
+ resolve(randomJoke);
133
+ });
134
+
135
+ req.on('timeout', () => {
136
+ req.abort();
137
+ const randomJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
138
+ resolve(randomJoke);
139
+ });
140
+ });
141
+ }
142
+
143
+ static async showSpinner(promise, message = 'Procesando') {
144
+ const spinners = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
145
+ let spinnerIndex = 0;
146
+ let jokeCountdown = 10;
147
+ let currentJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
148
+ let isFinished = false;
149
+ let isFirstRender = true;
150
+
151
+ // Obtener primer chiste de la API sin bloquear
152
+ this.getJoke().then(joke => {
153
+ if (!isFinished) currentJoke = joke;
154
+ }).catch(() => {}); // Si falla, mantener el local
155
+
156
+ // Ocultar cursor
157
+ process.stdout.write('\x1B[?25l');
158
+
159
+ // Reservar espacio para las 3 líneas
160
+ process.stdout.write('\n\n\n');
161
+
162
+ const interval = setInterval(() => {
163
+ if (isFinished) {
164
+ clearInterval(interval);
165
+ return;
166
+ }
167
+
168
+ spinnerIndex++;
169
+
170
+ // Actualizar countdown cada segundo (10 iteraciones de 100ms)
171
+ if (spinnerIndex % 10 === 0) {
172
+ jokeCountdown--;
173
+
174
+ // Renovar chiste cada 10 segundos
175
+ if (jokeCountdown <= 0) {
176
+ this.getJoke().then(joke => {
177
+ if (!isFinished) currentJoke = joke;
178
+ }).catch(() => {
179
+ if (!isFinished) {
180
+ currentJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
181
+ }
182
+ });
183
+ jokeCountdown = 10;
184
+ }
185
+ }
186
+
187
+ // Siempre volver exactamente 3 líneas arriba
188
+ process.stdout.write('\x1B[3A');
189
+
190
+ // Renderizar las 3 líneas desde el inicio
191
+ const spinner = spinners[spinnerIndex % spinners.length];
192
+
193
+ // Línea 1: Spinner
194
+ process.stdout.write('\r\x1B[2K' + `${colors.yellow}${spinner} ${message}${colors.reset}\n`);
195
+
196
+ // Línea 2: Chiste
197
+ process.stdout.write('\r\x1B[2K' + `${colors.green}🎭 ${currentJoke}${colors.reset}\n`);
198
+
199
+ // Línea 3: Countdown
200
+ process.stdout.write('\r\x1B[2K' + `${colors.yellow}⏱️ Próximo chiste en: ${jokeCountdown}s${colors.reset}\n`);
201
+ }, 100);
202
+
203
+ try {
204
+ const result = await promise;
205
+ isFinished = true;
206
+ clearInterval(interval);
207
+
208
+ // Limpiar exactamente 3 líneas completamente
209
+ process.stdout.write('\x1B[3A'); // Subir 3 líneas
210
+ process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
211
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
212
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
213
+ process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
214
+ process.stdout.write('\r'); // Ir al inicio de línea
215
+
216
+ // Mostrar cursor
217
+ process.stdout.write('\x1B[?25h');
218
+
219
+ return result;
220
+ } catch (error) {
221
+ isFinished = true;
222
+ clearInterval(interval);
223
+
224
+ // Limpiar exactamente 3 líneas completamente
225
+ process.stdout.write('\x1B[3A'); // Subir 3 líneas
226
+ process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
227
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
228
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
229
+ process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
230
+ process.stdout.write('\r'); // Ir al inicio de línea
231
+
232
+ // Mostrar cursor
233
+ process.stdout.write('\x1B[?25h');
234
+
235
+ throw error;
236
+ }
237
+ }
238
+ }
239
+
38
240
  // Verificar si estamos en un repositorio git
39
241
  function checkGitRepo() {
40
242
  try {
@@ -51,13 +253,33 @@ function getTemplatesPath() {
51
253
  }
52
254
 
53
255
  // Comando install
54
- function install(args) {
256
+ async function install(args) {
55
257
  if (!checkGitRepo()) {
56
258
  error('No estás en un repositorio Git. Por favor, ejecuta este comando desde la raíz de un repositorio.');
57
259
  }
58
260
 
59
261
  info('Instalando Claude Git Hooks...');
60
262
 
263
+ // Solicitar contraseña sudo al inicio si es necesario
264
+ let sudoPassword = null;
265
+ if (os.platform() === 'linux') {
266
+ const needsInstall = await checkIfInstallationNeeded();
267
+ if (needsInstall) {
268
+ info('Para la instalación automática de dependencias se necesita acceso sudo, por favor ingrese contraseña');
269
+ sudoPassword = await readPassword('Introduce tu contraseña de Ubuntu para sudo: ');
270
+
271
+ if (sudoPassword && !testSudoPassword(sudoPassword)) {
272
+ warning('Contraseña incorrecta. Continuando sin instalación automática.');
273
+ sudoPassword = null;
274
+ } else if (sudoPassword) {
275
+ success('Contraseña verificada. Procediendo con instalación automática.');
276
+ }
277
+ }
278
+ }
279
+
280
+ // Verificar dependencias con instalación automática
281
+ await checkAndInstallDependencies(sudoPassword);
282
+
61
283
  const templatesPath = getTemplatesPath();
62
284
  const hooksPath = '.git/hooks';
63
285
 
@@ -91,13 +313,15 @@ function install(args) {
91
313
  guidelines.forEach(guideline => {
92
314
  if (!fs.existsSync(guideline)) {
93
315
  const sourcePath = path.join(templatesPath, guideline);
94
- fs.copyFileSync(sourcePath, guideline);
95
- success(`${guideline} creado`);
316
+ if (fs.existsSync(sourcePath)) {
317
+ fs.copyFileSync(sourcePath, guideline);
318
+ success(`${guideline} creado`);
319
+ }
96
320
  }
97
321
  });
98
322
 
99
- // Verificar dependencias
100
- checkDependencies();
323
+ // Configurar Git
324
+ configureGit();
101
325
 
102
326
  success('¡Claude Git Hooks instalado exitosamente! 🎉');
103
327
  console.log('\nUso:');
@@ -106,31 +330,198 @@ function install(args) {
106
330
  console.log('\nPara más opciones: claude-hooks --help');
107
331
  }
108
332
 
109
- // Verificar dependencias
110
- function checkDependencies() {
111
- info('\nVerificando dependencias...');
333
+ // Verificar dependencias completas (como setup-wsl.sh)
334
+ async function checkAndInstallDependencies(sudoPassword = null) {
335
+ info('Verificando dependencias del sistema...');
336
+
337
+ // Verificar Node.js
338
+ try {
339
+ const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim();
340
+ success(`Node.js ${nodeVersion}`);
341
+ } catch (e) {
342
+ error('Node.js no está instalado. Instala Node.js e intenta de nuevo.');
343
+ }
344
+
345
+ // Verificar npm
346
+ try {
347
+ const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim();
348
+ success(`npm ${npmVersion}`);
349
+ } catch (e) {
350
+ error('npm no está instalado.');
351
+ }
352
+
353
+ // Verificar e instalar jq
354
+ try {
355
+ const jqVersion = execSync('jq --version', { encoding: 'utf8' }).trim();
356
+ success(`jq ${jqVersion}`);
357
+ } catch (e) {
358
+ warning('jq no está instalado. Instalando...');
359
+ if (installPackage('jq', sudoPassword)) {
360
+ success('jq instalado correctamente');
361
+ } else {
362
+ warning('No se pudo instalar jq automáticamente');
363
+ if (os.platform() === 'linux') {
364
+ console.log('Instálalo manualmente con: sudo apt install jq');
365
+ } else if (os.platform() === 'darwin') {
366
+ console.log('Instálalo manualmente con: brew install jq');
367
+ }
368
+ }
369
+ }
370
+
371
+ // Verificar e instalar curl
372
+ try {
373
+ const curlVersion = execSync('curl --version', { encoding: 'utf8' }).split('\n')[0];
374
+ success(`curl ${curlVersion.split(' ')[1]}`);
375
+ } catch (e) {
376
+ warning('curl no está instalado. Instalando...');
377
+ if (installPackage('curl', sudoPassword)) {
378
+ success('curl instalado correctamente');
379
+ } else {
380
+ warning('No se pudo instalar curl automáticamente');
381
+ if (os.platform() === 'linux') {
382
+ console.log('Instálalo manualmente con: sudo apt install curl');
383
+ } else if (os.platform() === 'darwin') {
384
+ console.log('Instálalo manualmente con: brew install curl');
385
+ }
386
+ }
387
+ }
388
+
389
+ // Verificar Git
390
+ try {
391
+ const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
392
+ success(`${gitVersion}`);
393
+ } catch (e) {
394
+ error('Git no está instalado. Instala Git e intenta de nuevo.');
395
+ }
396
+
397
+ // Verificar herramientas Unix estándar
398
+ const unixTools = ['sed', 'awk', 'grep', 'head', 'tail', 'stat', 'tput'];
399
+ const missingTools = [];
400
+
401
+ unixTools.forEach(tool => {
402
+ try {
403
+ execSync(`which ${tool}`, { stdio: 'ignore' });
404
+ } catch (e) {
405
+ missingTools.push(tool);
406
+ }
407
+ });
408
+
409
+ if (missingTools.length === 0) {
410
+ success('Herramientas Unix estándar verificadas');
411
+ } else {
412
+ error(`Faltan herramientas Unix estándar: ${missingTools.join(', ')}`);
413
+ }
414
+
415
+ // Verificar e instalar Claude CLI
416
+ await checkAndInstallClaude();
417
+
418
+ // Verificar autenticación de Claude
419
+ await checkClaudeAuth();
420
+
421
+ // Limpiar contraseña de memoria
422
+ sudoPassword = null;
423
+ }
424
+
425
+ // Verificar si necesitamos instalar dependencias
426
+ async function checkIfInstallationNeeded() {
427
+ const dependencies = ['jq', 'curl'];
428
+
429
+ for (const dep of dependencies) {
430
+ try {
431
+ execSync(`which ${dep}`, { stdio: 'ignore' });
432
+ } catch (e) {
433
+ return true; // Necesita instalación
434
+ }
435
+ }
112
436
 
113
437
  // Verificar Claude CLI
114
438
  try {
115
439
  execSync('claude --version', { stdio: 'ignore' });
116
- success('Claude CLI encontrado');
117
440
  } catch (e) {
118
- warning('Claude CLI no está instalado');
119
- console.log('Instálalo con: npm install -g @anthropic-ai/claude-cli');
120
- console.log('Luego ejecuta: claude auth login');
441
+ return true; // Necesita instalación de Claude
121
442
  }
122
443
 
123
- // Verificar jq
444
+ return false;
445
+ }
446
+
447
+ // Verificar e instalar Claude CLI
448
+ async function checkAndInstallClaude() {
124
449
  try {
125
- execSync('jq --version', { stdio: 'ignore' });
126
- success('jq encontrado');
450
+ execSync('claude --version', { stdio: 'ignore' });
451
+ success('Claude CLI detectado');
452
+ } catch (e) {
453
+ info('Claude CLI no detectado. Instalando...');
454
+ try {
455
+ execSync('npm install -g @anthropic-ai/claude-cli', { stdio: 'inherit' });
456
+ success('Claude CLI instalado correctamente');
457
+ } catch (installError) {
458
+ error('Error al instalar Claude CLI. Instálalo manualmente: npm install -g @anthropic-ai/claude-cli');
459
+ }
460
+ }
461
+ }
462
+
463
+ // Verificar autenticación de Claude con entretenimiento
464
+ async function checkClaudeAuth() {
465
+ info('Verificando autenticación de Claude...');
466
+
467
+ // Usar spawn para no bloquear, pero con stdio: 'ignore' como el original
468
+ const authPromise = new Promise((resolve, reject) => {
469
+ const child = spawn('claude', ['auth', 'status'], {
470
+ stdio: 'ignore', // Igual que el original
471
+ detached: false,
472
+ windowsHide: true
473
+ });
474
+
475
+ // Timeout manual ya que spawn no tiene timeout nativo
476
+ const timeout = setTimeout(() => {
477
+ child.kill();
478
+ reject(new Error('timeout'));
479
+ }, 120000); // 2 minutos
480
+
481
+ child.on('exit', (code) => {
482
+ clearTimeout(timeout);
483
+ if (code === 0) {
484
+ resolve('success');
485
+ } else {
486
+ reject(new Error('not_authenticated'));
487
+ }
488
+ });
489
+
490
+ child.on('error', (err) => {
491
+ clearTimeout(timeout);
492
+ reject(err);
493
+ });
494
+ });
495
+
496
+ try {
497
+ await Entertainment.showSpinner(authPromise, 'Verificando autenticación de Claude');
498
+ success('Autenticado en Claude');
127
499
  } catch (e) {
128
- warning('jq no está instalado');
129
- if (os.platform() === 'linux') {
130
- console.log('Instálalo con: sudo apt install jq');
131
- } else if (os.platform() === 'darwin') {
132
- console.log('Instálalo con: brew install jq');
500
+ warning('No estás autenticado en Claude');
501
+ console.log('Ejecuta: claude auth login');
502
+ console.log('Después vuelve a ejecutar este comando');
503
+ }
504
+ }
505
+
506
+ // Configurar Git (line endings, etc.)
507
+ function configureGit() {
508
+ info('Configurando Git...');
509
+
510
+ try {
511
+ // Configurar line endings para WSL
512
+ execSync('git config core.autocrlf input', { stdio: 'ignore' });
513
+ success('Line endings configurados para WSL (core.autocrlf = input)');
514
+
515
+ // Intentar configurar en Windows a través de PowerShell
516
+ try {
517
+ execSync('powershell.exe -Command "git config core.autocrlf true"', { stdio: 'ignore' });
518
+ success('Line endings configurados para Windows (core.autocrlf = true)');
519
+ } catch (psError) {
520
+ info('No se pudo configurar automáticamente en Windows');
133
521
  }
522
+
523
+ } catch (e) {
524
+ warning('Error al configurar Git');
134
525
  }
135
526
  }
136
527
 
@@ -315,35 +706,42 @@ Más información: https://github.com/pablorovito/claude-git-hooks
315
706
  }
316
707
 
317
708
  // Main
318
- const args = process.argv.slice(2);
319
- const command = args[0];
320
-
321
- switch (command) {
322
- case 'install':
323
- install(args.slice(1));
324
- break;
325
- case 'uninstall':
326
- uninstall();
327
- break;
328
- case 'enable':
329
- enable(args[1]);
330
- break;
331
- case 'disable':
332
- disable(args[1]);
333
- break;
334
- case 'set-mode':
335
- setMode(args[1]);
336
- break;
337
- case 'status':
338
- status();
339
- break;
340
- case 'help':
341
- case '--help':
342
- case '-h':
343
- case undefined:
344
- showHelp();
345
- break;
346
- default:
347
- error(`Comando desconocido: ${command}`);
348
- showHelp();
349
- }
709
+ async function main() {
710
+ const args = process.argv.slice(2);
711
+ const command = args[0];
712
+
713
+ switch (command) {
714
+ case 'install':
715
+ await install(args.slice(1));
716
+ break;
717
+ case 'uninstall':
718
+ uninstall();
719
+ break;
720
+ case 'enable':
721
+ enable(args[1]);
722
+ break;
723
+ case 'disable':
724
+ disable(args[1]);
725
+ break;
726
+ case 'set-mode':
727
+ setMode(args[1]);
728
+ break;
729
+ case 'status':
730
+ status();
731
+ break;
732
+ case 'help':
733
+ case '--help':
734
+ case '-h':
735
+ case undefined:
736
+ showHelp();
737
+ break;
738
+ default:
739
+ error(`Comando desconocido: ${command}`);
740
+ showHelp();
741
+ }
742
+ }
743
+
744
+ // Ejecutar main
745
+ main().catch(err => {
746
+ error(`Error inesperado: ${err.message}`);
747
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-git-hooks",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Git hooks con Claude CLI para análisis de código y generación automática de mensajes de commit",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -35,6 +35,7 @@
35
35
  "lib/",
36
36
  "templates/",
37
37
  "README.md",
38
+ "CHANGELOG.md",
38
39
  "LICENSE"
39
40
  ]
40
41
  }