@deinossrl/dgp-agent 1.4.40 → 1.4.42

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