@deinossrl/dgp-agent 1.4.40 → 1.4.41

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 (3) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/index.mjs +255 -3
  3. package/package.json +4 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog - DGP Agent
2
+
3
+ ## [1.4.41] - 2026-01-12
4
+
5
+ ### Added
6
+ - Soporte completo para comandos `pg_dump` desde la plataforma web
7
+ - Integración con Supabase Storage para subir backups automáticamente
8
+ - Nueva dependencia: `@supabase/supabase-js` v2.39.0
9
+
10
+ ### Fixed
11
+ - Corregido schema de comandos: ahora busca en `dgp.agent_commands` correctamente
12
+ - Agregados headers `Accept-Profile` y `Content-Profile` para acceso al schema dgp
13
+ - URL de comandos actualizada de `dgp.agent_commands` a `agent_commands` con headers de schema
14
+
15
+ ### Changed
16
+ - Mejorado manejo de comandos con soporte para múltiples schemas en Supabase
17
+
18
+ ## [1.4.40] - 2026-01-10
19
+
20
+ ### Previous Release
21
+ - Funcionalidad base de reporte de estado Git
22
+ - Comandos deploy, git_commit_push, test_connection
23
+ - Integración con IA (Claude) para tareas automatizadas
package/index.mjs CHANGED
@@ -25,9 +25,10 @@
25
25
 
26
26
  import { execSync, spawn, spawnSync } from 'child_process';
27
27
  import { hostname, homedir } from 'os';
28
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, createWriteStream } from 'fs';
28
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, createWriteStream, statSync, unlinkSync } from 'fs';
29
29
  import { join, dirname } from 'path';
30
30
  import https from 'https';
31
+ import { createClient } from '@supabase/supabase-js';
31
32
 
32
33
  // ============================================
33
34
  // CONFIG FILE MANAGEMENT
@@ -352,13 +353,13 @@ function getPlinkPath() {
352
353
  }
353
354
 
354
355
  // Versión del agente
355
- const AGENT_VERSION = '1.4.40';
356
+ const AGENT_VERSION = '1.4.41';
356
357
  let AGENT_MODE = 'smart'; // Siempre inteligente
357
358
 
358
359
  // Configuración (prioridad: env vars > archivo config > platform config > defaults)
359
360
  const CONFIG = {
360
361
  apiUrl: process.env.DGP_API_URL || fileConfig.apiUrl || 'https://asivayhbrqennwiwttds.supabase.co/functions/v1/dgp-agent-status',
361
- commandsUrl: process.env.DGP_COMMANDS_URL || fileConfig.commandsUrl || 'https://asivayhbrqennwiwttds.supabase.co/rest/v1/dgp_agent_commands',
362
+ commandsUrl: process.env.DGP_COMMANDS_URL || fileConfig.commandsUrl || 'https://asivayhbrqennwiwttds.supabase.co/rest/v1/agent_commands',
362
363
  supabaseKey: process.env.DGP_SUPABASE_KEY || fileConfig.supabaseKey || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFzaXZheWhicnFlbm53aXd0dGRzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjczMDAwOTcsImV4cCI6MjA4Mjg3NjA5N30.s3a7dR-dPkEXI7B2lUTUXU69923hhuX6meheNeo5EKA',
363
364
  interval: parseInt(process.env.DGP_INTERVAL || fileConfig.interval || '30', 10),
364
365
  commandPollInterval: parseInt(process.env.DGP_COMMAND_POLL_INTERVAL || fileConfig.commandPollInterval || '10', 10),
@@ -783,6 +784,8 @@ async function runAIMode() {
783
784
  headers: {
784
785
  'apikey': CONFIG.supabaseKey,
785
786
  'Authorization': `Bearer ${CONFIG.supabaseKey}`,
787
+ 'Accept-Profile': 'dgp',
788
+ 'Content-Profile': 'dgp',
786
789
  },
787
790
  });
788
791
 
@@ -1119,6 +1122,8 @@ async function getPendingCommands() {
1119
1122
  headers: {
1120
1123
  'apikey': CONFIG.supabaseKey,
1121
1124
  'Authorization': `Bearer ${CONFIG.supabaseKey}`,
1125
+ 'Accept-Profile': 'dgp',
1126
+ 'Content-Profile': 'dgp',
1122
1127
  },
1123
1128
  });
1124
1129
 
@@ -1170,6 +1175,8 @@ async function updateCommandLogs(commandId, logs) {
1170
1175
  'Authorization': `Bearer ${CONFIG.supabaseKey}`,
1171
1176
  'Content-Type': 'application/json',
1172
1177
  'Prefer': 'return=minimal',
1178
+ 'Accept-Profile': 'dgp',
1179
+ 'Content-Profile': 'dgp',
1173
1180
  },
1174
1181
  body: JSON.stringify({
1175
1182
  result: { logs: logs },
@@ -1409,6 +1416,246 @@ async function executeDeploy(command) {
1409
1416
  }
1410
1417
  }
1411
1418
 
1419
+ /**
1420
+ * Ejecuta pg_dump para crear backup de PostgreSQL
1421
+ */
1422
+ async function executePgDump(command) {
1423
+ const { id, params } = command;
1424
+ const backupId = params.backup_id;
1425
+ const environmentId = params.environment_id;
1426
+
1427
+ logCommand(`=== Ejecutando pg_dump (Backup) ===`);
1428
+ logInfo(`Backup ID: ${backupId}`);
1429
+ logInfo(`Environment ID: ${environmentId}`);
1430
+
1431
+ // Iniciar tracking de logs
1432
+ currentCommandId = id;
1433
+ commandLogs = [];
1434
+
1435
+ // Crear directorio para backups si no existe
1436
+ const backupsDir = join(CONFIG_DIR, 'backups');
1437
+ if (!existsSync(backupsDir)) {
1438
+ mkdirSync(backupsDir, { recursive: true });
1439
+ }
1440
+
1441
+ let localFilePath = null;
1442
+ let storagePath = null;
1443
+
1444
+ try {
1445
+ await updateCommandStatus(id, 'running', {});
1446
+ await addCommandLog('info', 'Iniciando backup de base de datos...');
1447
+
1448
+ // Determinar extensión según formato
1449
+ const format = params.format || 'custom';
1450
+ const extension = format === 'custom' ? 'dump' : format === 'tar' ? 'tar' : 'sql';
1451
+
1452
+ // Generar nombre de archivo local
1453
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
1454
+ const fileName = `backup_${environmentId}_${timestamp}.${extension}`;
1455
+ localFilePath = join(backupsDir, fileName);
1456
+
1457
+ // Construir comando pg_dump
1458
+ let pgDumpCmd = 'pg_dump';
1459
+ const pgDumpArgs = [];
1460
+
1461
+ // Configurar conexión según tipo
1462
+ if (params.connection_type === 'postgresql') {
1463
+ // PostgreSQL genérico
1464
+ pgDumpArgs.push(`--host=${params.host}`);
1465
+ pgDumpArgs.push(`--port=${params.port || 5432}`);
1466
+ pgDumpArgs.push(`--username=${params.username}`);
1467
+ pgDumpArgs.push(`--dbname=${params.database}`);
1468
+
1469
+ // Si hay password, configurar PGPASSWORD
1470
+ if (params.password_encrypted) {
1471
+ // TODO: Desencriptar password si está encriptado
1472
+ process.env.PGPASSWORD = params.password_encrypted;
1473
+ }
1474
+ } else {
1475
+ // Supabase
1476
+ const projectRef = params.project_ref;
1477
+ pgDumpArgs.push(`--host=db.${projectRef}.supabase.co`);
1478
+ pgDumpArgs.push(`--port=5432`);
1479
+ pgDumpArgs.push(`--username=postgres`);
1480
+ pgDumpArgs.push(`--dbname=postgres`);
1481
+
1482
+ // Para Supabase, necesitamos el service_role_key como password
1483
+ if (params.service_role_key) {
1484
+ process.env.PGPASSWORD = params.service_role_key;
1485
+ }
1486
+ }
1487
+
1488
+ // Opciones de formato
1489
+ if (format === 'custom') {
1490
+ pgDumpArgs.push('--format=custom');
1491
+ } else if (format === 'tar') {
1492
+ pgDumpArgs.push('--format=tar');
1493
+ } else {
1494
+ pgDumpArgs.push('--format=plain');
1495
+ }
1496
+
1497
+ // Opciones de contenido
1498
+ if (params.backup_type === 'schema') {
1499
+ pgDumpArgs.push('--schema-only');
1500
+ } else if (params.backup_type === 'data') {
1501
+ pgDumpArgs.push('--data-only');
1502
+ }
1503
+
1504
+ // Incluir opciones adicionales
1505
+ if (params.include_privileges) {
1506
+ pgDumpArgs.push('--no-privileges');
1507
+ }
1508
+ if (params.include_owner) {
1509
+ pgDumpArgs.push('--no-owner');
1510
+ }
1511
+
1512
+ // Schemas específicos
1513
+ if (params.schemas && params.schemas.length > 0) {
1514
+ params.schemas.forEach(schema => {
1515
+ pgDumpArgs.push(`--schema=${schema}`);
1516
+ });
1517
+ }
1518
+
1519
+ // Tablas específicas
1520
+ if (params.tables && params.tables.length > 0) {
1521
+ params.tables.forEach(table => {
1522
+ pgDumpArgs.push(`--table=${table}`);
1523
+ });
1524
+ }
1525
+
1526
+ // Archivo de salida
1527
+ pgDumpArgs.push(`--file=${localFilePath}`);
1528
+
1529
+ // Ejecutar pg_dump
1530
+ const fullCommand = `${pgDumpCmd} ${pgDumpArgs.join(' ')}`;
1531
+ logCommand(`Ejecutando: pg_dump [conexión oculta]`);
1532
+ await addCommandLog('command', 'Ejecutando pg_dump...');
1533
+
1534
+ await shellAsync(fullCommand);
1535
+
1536
+ // Verificar que el archivo se creó
1537
+ if (!existsSync(localFilePath)) {
1538
+ throw new Error('El archivo de backup no se generó correctamente');
1539
+ }
1540
+
1541
+ const fileSize = statSync(localFilePath).size;
1542
+ logSuccess(`Backup creado localmente (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
1543
+ await addCommandLog('success', `Backup creado: ${(fileSize / 1024 / 1024).toFixed(2)} MB`);
1544
+
1545
+ // Subir a Supabase Storage
1546
+ logInfo('Subiendo backup a Supabase Storage...');
1547
+ await addCommandLog('info', 'Subiendo a Storage...');
1548
+
1549
+ const supabase = createClient(
1550
+ CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
1551
+ CONFIG.supabaseKey
1552
+ );
1553
+
1554
+ // Usar storage_path de los params (ya definido en la DB)
1555
+ storagePath = params.storage_path;
1556
+ if (!storagePath) {
1557
+ throw new Error('storage_path no definido en los parámetros');
1558
+ }
1559
+
1560
+ // Leer archivo y subirlo
1561
+ const fileBuffer = readFileSync(localFilePath);
1562
+ const { data: uploadData, error: uploadError } = await supabase.storage
1563
+ .from('database-backups')
1564
+ .upload(storagePath, fileBuffer, {
1565
+ contentType: format === 'sql' ? 'application/sql' : 'application/octet-stream',
1566
+ upsert: false
1567
+ });
1568
+
1569
+ if (uploadError) {
1570
+ throw new Error(`Error subiendo a Storage: ${uploadError.message}`);
1571
+ }
1572
+
1573
+ logSuccess(`Archivo subido a Storage: ${storagePath}`);
1574
+ await addCommandLog('success', `Subido a: ${storagePath}`);
1575
+
1576
+ // Actualizar registro en db_backups
1577
+ logInfo('Actualizando registro de backup...');
1578
+ const { error: updateError } = await supabase
1579
+ .schema('dgp')
1580
+ .from('db_backups')
1581
+ .update({
1582
+ status: 'completed',
1583
+ storage_path: storagePath,
1584
+ size_bytes: fileSize,
1585
+ completed_at: new Date().toISOString()
1586
+ })
1587
+ .eq('id', backupId);
1588
+
1589
+ if (updateError) {
1590
+ logError(`Error actualizando registro: ${updateError.message}`);
1591
+ await addCommandLog('error', `Error actualizando DB: ${updateError.message}`);
1592
+ } else {
1593
+ logSuccess('Registro de backup actualizado');
1594
+ await addCommandLog('success', 'Backup registrado en DB');
1595
+ }
1596
+
1597
+ // Limpiar archivo local (opcional)
1598
+ try {
1599
+ unlinkSync(localFilePath);
1600
+ logInfo('Archivo local eliminado');
1601
+ } catch (e) {
1602
+ // Ignorar errores al eliminar
1603
+ }
1604
+
1605
+ // Marcar comando como completado
1606
+ await updateCommandStatus(id, 'completed', {
1607
+ backup_id: backupId,
1608
+ storage_path: storagePath,
1609
+ size_bytes: fileSize,
1610
+ local_path: localFilePath
1611
+ });
1612
+
1613
+ console.log('');
1614
+ logSuccess(`=== Backup completado exitosamente ===`);
1615
+ console.log('');
1616
+
1617
+ return { success: true };
1618
+
1619
+ } catch (error) {
1620
+ logError(`Backup failed: ${error.message}`);
1621
+ await addCommandLog('error', error.message);
1622
+
1623
+ // Limpiar archivo local si existe
1624
+ if (localFilePath && existsSync(localFilePath)) {
1625
+ try {
1626
+ unlinkSync(localFilePath);
1627
+ } catch (e) {
1628
+ // Ignorar
1629
+ }
1630
+ }
1631
+
1632
+ // Actualizar estado del backup en DB
1633
+ try {
1634
+ const supabase = createClient(
1635
+ CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
1636
+ CONFIG.supabaseKey
1637
+ );
1638
+
1639
+ await supabase
1640
+ .schema('dgp')
1641
+ .from('db_backups')
1642
+ .update({
1643
+ status: 'failed',
1644
+ error_message: error.message
1645
+ })
1646
+ .eq('id', backupId);
1647
+ } catch (e) {
1648
+ // Ignorar errores al actualizar
1649
+ }
1650
+
1651
+ await updateCommandStatus(id, 'failed', {}, error.message);
1652
+ return { success: false, error: error.message };
1653
+ } finally {
1654
+ // Limpiar variables de entorno
1655
+ delete process.env.PGPASSWORD;
1656
+ }
1657
+ }
1658
+
1412
1659
  /**
1413
1660
  * Ejecuta un comando recibido
1414
1661
  * @param {Object} command - El comando a ejecutar
@@ -1432,6 +1679,9 @@ async function executeCommand(command, useAI = false) {
1432
1679
  case 'test_connection':
1433
1680
  return await executeTestConnection(command);
1434
1681
 
1682
+ case 'pg_dump':
1683
+ return await executePgDump(command);
1684
+
1435
1685
  case 'rollback':
1436
1686
  logError('Rollback not implemented yet');
1437
1687
  await updateCommandStatus(command.id, 'failed', {}, 'Rollback not implemented');
@@ -1962,6 +2212,8 @@ async function runAgent() {
1962
2212
  headers: {
1963
2213
  'apikey': CONFIG.supabaseKey,
1964
2214
  'Authorization': `Bearer ${CONFIG.supabaseKey}`,
2215
+ 'Accept-Profile': 'dgp',
2216
+ 'Content-Profile': 'dgp',
1965
2217
  },
1966
2218
  });
1967
2219
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deinossrl/dgp-agent",
3
- "version": "1.4.40",
3
+ "version": "1.4.41",
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": {
@@ -29,5 +29,8 @@
29
29
  "homepage": "https://github.com/DEINOS-SRL/tenminuteia#readme",
30
30
  "engines": {
31
31
  "node": ">=18.0.0"
32
+ },
33
+ "dependencies": {
34
+ "@supabase/supabase-js": "^2.39.0"
32
35
  }
33
36
  }