@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.
- package/CHANGELOG.md +23 -0
- package/index.mjs +255 -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.
|
|
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/
|
|
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.
|
|
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
|
}
|