@deinossrl/dgp-agent 1.2.6 β†’ 1.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.
Files changed (2) hide show
  1. package/index.mjs +310 -18
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -35,6 +35,9 @@ const CONFIG = {
35
35
  commandPollInterval: parseInt(process.env.DGP_COMMAND_POLL_INTERVAL || '10', 10),
36
36
  machineId: process.env.DGP_MACHINE_ID || `${hostname()}-${process.env.USERNAME || process.env.USER || 'dev'}`,
37
37
  authToken: process.env.DGP_AUTH_TOKEN || null,
38
+ // Claude AI config
39
+ anthropicApiKey: process.env.ANTHROPIC_API_KEY || null,
40
+ aiModel: process.env.DGP_AI_MODEL || 'claude-sonnet-4-20250514',
38
41
  };
39
42
 
40
43
  // Colores para la consola
@@ -71,6 +74,290 @@ function logCommand(message) {
71
74
  log(message, 'magenta');
72
75
  }
73
76
 
77
+ function logAI(message) {
78
+ log(`πŸ€– ${message}`, 'cyan');
79
+ }
80
+
81
+ // ============================================
82
+ // CLAUDE AI INTEGRATION
83
+ // ============================================
84
+
85
+ /**
86
+ * Llama a la API de Claude
87
+ */
88
+ async function callClaude(prompt, systemPrompt = null) {
89
+ if (!CONFIG.anthropicApiKey) {
90
+ throw new Error('ANTHROPIC_API_KEY not configured. Set it as environment variable.');
91
+ }
92
+
93
+ const messages = [{ role: 'user', content: prompt }];
94
+
95
+ const body = {
96
+ model: CONFIG.aiModel,
97
+ max_tokens: 4096,
98
+ messages,
99
+ };
100
+
101
+ if (systemPrompt) {
102
+ body.system = systemPrompt;
103
+ }
104
+
105
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ 'x-api-key': CONFIG.anthropicApiKey,
110
+ 'anthropic-version': '2023-06-01',
111
+ },
112
+ body: JSON.stringify(body),
113
+ });
114
+
115
+ if (!response.ok) {
116
+ const text = await response.text();
117
+ throw new Error(`Claude API error: ${response.status} - ${text}`);
118
+ }
119
+
120
+ const data = await response.json();
121
+ return data.content[0].text;
122
+ }
123
+
124
+ /**
125
+ * Analiza el repositorio y genera contexto para IA
126
+ */
127
+ function getRepoContext() {
128
+ const context = {
129
+ cwd: process.cwd(),
130
+ machineId: CONFIG.machineId,
131
+ git: {},
132
+ project: {},
133
+ files: [],
134
+ };
135
+
136
+ // Info de git
137
+ try {
138
+ context.git.branch = shellSync('git branch --show-current').trim();
139
+ context.git.remoteUrl = shellSync('git config --get remote.origin.url').trim();
140
+ context.git.lastCommit = shellSync('git log -1 --oneline').trim();
141
+ context.git.status = shellSync('git status --short').trim();
142
+ } catch (e) {
143
+ context.git.error = e.message;
144
+ }
145
+
146
+ // Detectar tipo de proyecto
147
+ try {
148
+ const files = shellSync('ls -la').split('\n').slice(1);
149
+ context.files = files.map(f => f.split(/\s+/).pop()).filter(Boolean);
150
+
151
+ if (context.files.includes('package.json')) {
152
+ context.project.type = 'node';
153
+ try {
154
+ const pkg = JSON.parse(shellSync('cat package.json'));
155
+ context.project.name = pkg.name;
156
+ context.project.scripts = Object.keys(pkg.scripts || {});
157
+ context.project.dependencies = Object.keys(pkg.dependencies || {});
158
+ context.project.devDependencies = Object.keys(pkg.devDependencies || {});
159
+ } catch (e) {}
160
+ }
161
+
162
+ if (context.files.includes('requirements.txt') || context.files.includes('setup.py')) {
163
+ context.project.type = 'python';
164
+ }
165
+
166
+ if (context.files.includes('Cargo.toml')) {
167
+ context.project.type = 'rust';
168
+ }
169
+
170
+ if (context.files.includes('go.mod')) {
171
+ context.project.type = 'go';
172
+ }
173
+
174
+ // Detectar frameworks
175
+ if (context.project.dependencies) {
176
+ if (context.project.dependencies.includes('react')) context.project.framework = 'react';
177
+ if (context.project.dependencies.includes('vue')) context.project.framework = 'vue';
178
+ if (context.project.dependencies.includes('next')) context.project.framework = 'nextjs';
179
+ if (context.project.dependencies.includes('vite')) context.project.bundler = 'vite';
180
+ }
181
+
182
+ // Detectar CI/CD
183
+ if (context.files.includes('.github')) {
184
+ context.project.ci = 'github-actions';
185
+ }
186
+ if (context.files.includes('.gitlab-ci.yml')) {
187
+ context.project.ci = 'gitlab-ci';
188
+ }
189
+
190
+ } catch (e) {
191
+ context.filesError = e.message;
192
+ }
193
+
194
+ return context;
195
+ }
196
+
197
+ /**
198
+ * Ejecuta una tarea usando IA
199
+ */
200
+ async function executeAITask(task) {
201
+ logAI(`Analizando tarea: "${task}"`);
202
+
203
+ const context = getRepoContext();
204
+
205
+ const systemPrompt = `Eres un agente DevOps inteligente que ejecuta tareas en repositorios.
206
+ Tu trabajo es analizar la tarea solicitada y devolver los comandos shell que se deben ejecutar.
207
+
208
+ IMPORTANTE:
209
+ - Solo devuelve comandos que sean seguros de ejecutar
210
+ - No hagas cambios destructivos sin confirmaciΓ³n
211
+ - Usa el contexto del proyecto para elegir los comandos correctos
212
+ - Responde SOLO con JSON vΓ‘lido, sin explicaciones adicionales
213
+
214
+ Formato de respuesta:
215
+ {
216
+ "analysis": "breve explicaciΓ³n de lo que vas a hacer",
217
+ "commands": ["comando1", "comando2"],
218
+ "requires_confirmation": true/false,
219
+ "warning": "mensaje de advertencia si aplica"
220
+ }`;
221
+
222
+ const prompt = `Contexto del repositorio:
223
+ ${JSON.stringify(context, null, 2)}
224
+
225
+ Tarea a ejecutar: ${task}
226
+
227
+ Devuelve los comandos necesarios en formato JSON.`;
228
+
229
+ try {
230
+ const response = await callClaude(prompt, systemPrompt);
231
+
232
+ // Parsear respuesta JSON
233
+ let result;
234
+ try {
235
+ // Extraer JSON de la respuesta (puede venir con markdown)
236
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
237
+ if (jsonMatch) {
238
+ result = JSON.parse(jsonMatch[0]);
239
+ } else {
240
+ throw new Error('No JSON found in response');
241
+ }
242
+ } catch (e) {
243
+ logError(`Failed to parse AI response: ${e.message}`);
244
+ console.log('Raw response:', response);
245
+ return { success: false, error: 'Invalid AI response' };
246
+ }
247
+
248
+ logAI(`AnΓ‘lisis: ${result.analysis}`);
249
+
250
+ if (result.warning) {
251
+ log(`⚠️ ${result.warning}`, 'yellow');
252
+ }
253
+
254
+ if (result.requires_confirmation) {
255
+ log('Esta operaciΓ³n requiere confirmaciΓ³n. Usa --force para ejecutar sin confirmar.', 'yellow');
256
+ console.log('\nComandos a ejecutar:');
257
+ result.commands.forEach((cmd, i) => console.log(` ${i + 1}. ${cmd}`));
258
+ return { success: true, requiresConfirmation: true, commands: result.commands };
259
+ }
260
+
261
+ // Ejecutar comandos
262
+ logAI('Ejecutando comandos...');
263
+ const results = [];
264
+
265
+ for (const cmd of result.commands) {
266
+ logCommand(`$ ${cmd}`);
267
+ try {
268
+ const output = await shellAsync(cmd);
269
+ logSuccess('βœ“ Completado');
270
+ if (output.trim()) {
271
+ console.log(colors.gray + output + colors.reset);
272
+ }
273
+ results.push({ cmd, success: true, output });
274
+ } catch (e) {
275
+ logError(`βœ— FallΓ³: ${e.message}`);
276
+ results.push({ cmd, success: false, error: e.message });
277
+ break; // Detener en el primer error
278
+ }
279
+ }
280
+
281
+ return { success: true, results };
282
+
283
+ } catch (e) {
284
+ logError(`AI task failed: ${e.message}`);
285
+ return { success: false, error: e.message };
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Modo interactivo con IA
291
+ */
292
+ async function runAIMode() {
293
+ console.log('');
294
+ console.log(`${colors.cyan}╔═══════════════════════════════════════════════════════╗${colors.reset}`);
295
+ console.log(`${colors.cyan}β•‘ DGP Agent - AI Mode πŸ€– β•‘${colors.reset}`);
296
+ console.log(`${colors.cyan}β•‘ Powered by Claude β•‘${colors.reset}`);
297
+ console.log(`${colors.cyan}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${colors.reset}`);
298
+ console.log('');
299
+
300
+ if (!CONFIG.anthropicApiKey) {
301
+ logError('ANTHROPIC_API_KEY not set.');
302
+ log('Set your API key: export ANTHROPIC_API_KEY=sk-ant-...', 'yellow');
303
+ process.exit(1);
304
+ }
305
+
306
+ // Analizar repo
307
+ logAI('Analizando repositorio...');
308
+ const context = getRepoContext();
309
+
310
+ console.log('');
311
+ console.log(`${colors.bold}Proyecto detectado:${colors.reset}`);
312
+ console.log(` Tipo: ${context.project.type || 'desconocido'}`);
313
+ console.log(` Framework: ${context.project.framework || 'ninguno'}`);
314
+ console.log(` Bundler: ${context.project.bundler || 'ninguno'}`);
315
+ console.log(` CI/CD: ${context.project.ci || 'no detectado'}`);
316
+ console.log(` Branch: ${context.git.branch}`);
317
+ console.log(` Scripts: ${context.project.scripts?.join(', ') || 'ninguno'}`);
318
+ console.log('');
319
+
320
+ // Obtener tarea de los argumentos
321
+ const taskArgs = process.argv.slice(3).join(' ');
322
+
323
+ if (taskArgs) {
324
+ // Ejecutar tarea pasada como argumento
325
+ await executeAITask(taskArgs);
326
+ } else {
327
+ // Modo interactivo
328
+ const readline = await import('readline');
329
+ const rl = readline.createInterface({
330
+ input: process.stdin,
331
+ output: process.stdout,
332
+ });
333
+
334
+ const askQuestion = () => {
335
+ rl.question(`${colors.cyan}πŸ€– ΒΏQuΓ© querΓ©s hacer? ${colors.reset}`, async (answer) => {
336
+ if (answer.toLowerCase() === 'exit' || answer.toLowerCase() === 'salir') {
337
+ console.log('Β‘Hasta luego!');
338
+ rl.close();
339
+ process.exit(0);
340
+ }
341
+
342
+ if (answer.trim()) {
343
+ await executeAITask(answer);
344
+ }
345
+
346
+ console.log('');
347
+ askQuestion();
348
+ });
349
+ };
350
+
351
+ log('EscribΓ­ "exit" o "salir" para terminar', 'gray');
352
+ console.log('');
353
+ askQuestion();
354
+ }
355
+ }
356
+
357
+ // ============================================
358
+ // GIT FUNCTIONS
359
+ // ============================================
360
+
74
361
  /**
75
362
  * Ejecuta un comando git y retorna el resultado
76
363
  */
@@ -236,7 +523,7 @@ async function reportStatus(status) {
236
523
  const payload = {
237
524
  machine_id: CONFIG.machineId,
238
525
  timestamp: new Date().toISOString(),
239
- agent_version: '1.2.5',
526
+ agent_version: '1.3.0',
240
527
  status,
241
528
  };
242
529
 
@@ -287,32 +574,29 @@ async function getPendingCommands() {
287
574
  }
288
575
 
289
576
  /**
290
- * Actualiza el estado de un comando
577
+ * Actualiza el estado de un comando via Edge Function
291
578
  */
292
579
  async function updateCommandStatus(commandId, status, result = {}, errorMessage = null) {
293
- const payload = {
294
- status,
295
- result,
296
- error_message: errorMessage,
297
- ...(status === 'running' ? { picked_up_at: new Date().toISOString() } : {}),
298
- ...(status === 'success' || status === 'failed' ? { completed_at: new Date().toISOString() } : {}),
299
- };
300
-
301
- const url = `${CONFIG.commandsUrl}?id=eq.${commandId}`;
580
+ const url = 'https://asivayhbrqennwiwttds.supabase.co/functions/v1/dgp-agent-command-update';
302
581
 
303
582
  const response = await fetch(url, {
304
- method: 'PATCH',
583
+ method: 'POST',
305
584
  headers: {
306
585
  'apikey': CONFIG.supabaseKey,
307
586
  'Authorization': `Bearer ${CONFIG.supabaseKey}`,
308
587
  'Content-Type': 'application/json',
309
- 'Prefer': 'return=minimal',
310
588
  },
311
- body: JSON.stringify(payload),
589
+ body: JSON.stringify({
590
+ commandId,
591
+ status,
592
+ result,
593
+ error_message: errorMessage,
594
+ }),
312
595
  });
313
596
 
314
597
  if (!response.ok) {
315
- throw new Error(`Failed to update command: HTTP ${response.status}`);
598
+ const text = await response.text();
599
+ throw new Error(`Failed to update command: HTTP ${response.status} - ${text}`);
316
600
  }
317
601
  }
318
602
 
@@ -538,7 +822,7 @@ async function runAgent(deployMode = false) {
538
822
  console.log('');
539
823
  console.log(`${colors.green}╔═══════════════════════════════════════════════════════╗${colors.reset}`);
540
824
  console.log(`${colors.green}β•‘ DGP Agent - Despliegue-GPT Local Agent β•‘${colors.reset}`);
541
- console.log(`${colors.green}β•‘ @deinossrl/dgp-agent v1.2.6 β•‘${colors.reset}`);
825
+ console.log(`${colors.green}β•‘ @deinossrl/dgp-agent v1.3.0 β•‘${colors.reset}`);
542
826
  console.log(`${colors.green}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${colors.reset}`);
543
827
  console.log('');
544
828
 
@@ -654,7 +938,7 @@ async function showStatus() {
654
938
  function showHelp() {
655
939
  console.log(`
656
940
  ${colors.bold}${colors.cyan}DGP Agent - Despliegue-GPT Local Agent${colors.reset}
657
- ${colors.gray}@deinossrl/dgp-agent v1.2.6${colors.reset}
941
+ ${colors.gray}@deinossrl/dgp-agent v1.3.0${colors.reset}
658
942
 
659
943
  ${colors.bold}DESCRIPCIΓ“N${colors.reset}
660
944
  Agente local que reporta el estado de tu repositorio Git
@@ -668,6 +952,8 @@ ${colors.bold}USO${colors.reset}
668
952
  ${colors.cyan}dgp-agent${colors.reset} Inicia el agente (solo reporta estado)
669
953
  ${colors.cyan}dgp-agent deploy${colors.reset} Modo deploy (reporta + escucha comandos)
670
954
  ${colors.cyan}dgp-agent status${colors.reset} Muestra el estado actual una vez
955
+ ${colors.cyan}dgp-agent ai${colors.reset} Modo IA interactivo (requiere ANTHROPIC_API_KEY)
956
+ ${colors.cyan}dgp-agent ai "tarea"${colors.reset} Ejecuta una tarea con IA
671
957
  ${colors.cyan}dgp-agent help${colors.reset} Muestra esta ayuda
672
958
 
673
959
  ${colors.bold}MODOS DE OPERACIΓ“N${colors.reset}
@@ -702,6 +988,8 @@ ${colors.bold}REQUISITOS PARA DEPLOY${colors.reset}
702
988
  - Permisos sudo para reload nginx (vΓ­a sudoers sin password)
703
989
 
704
990
  ${colors.bold}CHANGELOG${colors.reset}
991
+ ${colors.cyan}v1.3.0${colors.reset} - AI Mode: ejecuta tareas con lenguaje natural
992
+ ${colors.cyan}v1.2.7${colors.reset} - Fix: update via Edge Function (401 fix)
705
993
  ${colors.cyan}v1.2.6${colors.reset} - Comando git_commit_push desde la UI
706
994
  ${colors.cyan}v1.2.5${colors.reset} - SincronizaciΓ³n de versiones y changelog
707
995
  ${colors.cyan}v1.2.4${colors.reset} - Mejoras en ejecuciΓ³n async de comandos shell
@@ -737,7 +1025,11 @@ switch (command) {
737
1025
  case 'version':
738
1026
  case '-v':
739
1027
  case '--version':
740
- console.log('@deinossrl/dgp-agent v1.2.6');
1028
+ console.log('@deinossrl/dgp-agent v1.3.0');
1029
+ break;
1030
+ case 'ai':
1031
+ case '--ai':
1032
+ runAIMode();
741
1033
  break;
742
1034
  default:
743
1035
  runAgent(false); // Status-only mode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deinossrl/dgp-agent",
3
- "version": "1.2.6",
3
+ "version": "1.3.0",
4
4
  "description": "Agente local para Despliegue-GPT - Reporta el estado del repositorio Git a la plataforma TenMinute IA",
5
5
  "main": "index.mjs",
6
6
  "bin": {