@deinossrl/dgp-agent 1.0.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.
- package/index.mjs +370 -0
- package/package.json +33 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* DGP Agent - Agente local para Despliegue-GPT
|
|
4
|
+
* @deinos/dgp-agent
|
|
5
|
+
*
|
|
6
|
+
* Este agente corre en la máquina del desarrollador y:
|
|
7
|
+
* 1. Reporta el estado del repositorio git a la plataforma TenMinute IA
|
|
8
|
+
* 2. (Futuro) Ejecuta comandos enviados desde la plataforma
|
|
9
|
+
*
|
|
10
|
+
* Instalación:
|
|
11
|
+
* npm install -g @deinos/dgp-agent
|
|
12
|
+
*
|
|
13
|
+
* Uso:
|
|
14
|
+
* dgp-agent # Inicia el agente (reporta cada 30s)
|
|
15
|
+
* dgp-agent status # Muestra estado una vez
|
|
16
|
+
* dgp-agent help # Muestra ayuda
|
|
17
|
+
*
|
|
18
|
+
* Variables de entorno:
|
|
19
|
+
* DGP_API_URL URL del endpoint (default: TenMinute IA production)
|
|
20
|
+
* DGP_AUTH_TOKEN Token JWT para asociar a tu usuario
|
|
21
|
+
* DGP_INTERVAL Intervalo de reporte en segundos (default: 30)
|
|
22
|
+
* DGP_MACHINE_ID ID personalizado de la máquina
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { execSync } from 'child_process';
|
|
26
|
+
import { hostname } from 'os';
|
|
27
|
+
|
|
28
|
+
// Configuración
|
|
29
|
+
const CONFIG = {
|
|
30
|
+
apiUrl: process.env.DGP_API_URL || 'https://asivayhbrqennwiwttds.supabase.co/functions/v1/dgp-agent-status',
|
|
31
|
+
interval: parseInt(process.env.DGP_INTERVAL || '30', 10),
|
|
32
|
+
machineId: process.env.DGP_MACHINE_ID || `${hostname()}-${process.env.USERNAME || process.env.USER || 'dev'}`,
|
|
33
|
+
authToken: process.env.DGP_AUTH_TOKEN || null,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Colores para la consola
|
|
37
|
+
const colors = {
|
|
38
|
+
reset: '\x1b[0m',
|
|
39
|
+
green: '\x1b[32m',
|
|
40
|
+
yellow: '\x1b[33m',
|
|
41
|
+
blue: '\x1b[34m',
|
|
42
|
+
red: '\x1b[31m',
|
|
43
|
+
gray: '\x1b[90m',
|
|
44
|
+
cyan: '\x1b[36m',
|
|
45
|
+
bold: '\x1b[1m',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function log(message, color = 'reset') {
|
|
49
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
50
|
+
console.log(`${colors.gray}[${timestamp}]${colors.reset} ${colors[color]}${message}${colors.reset}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function logError(message) {
|
|
54
|
+
log(`ERROR: ${message}`, 'red');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function logSuccess(message) {
|
|
58
|
+
log(message, 'green');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function logInfo(message) {
|
|
62
|
+
log(message, 'blue');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ejecuta un comando git y retorna el resultado
|
|
67
|
+
*/
|
|
68
|
+
function git(command) {
|
|
69
|
+
try {
|
|
70
|
+
return execSync(`git ${command}`, {
|
|
71
|
+
encoding: 'utf-8',
|
|
72
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
73
|
+
}).trim();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Verifica si estamos en un repositorio git
|
|
81
|
+
*/
|
|
82
|
+
function isGitRepo() {
|
|
83
|
+
return git('rev-parse --is-inside-work-tree') === 'true';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Obtiene el estado actual del repositorio
|
|
88
|
+
*/
|
|
89
|
+
function getRepoStatus() {
|
|
90
|
+
const branch = git('rev-parse --abbrev-ref HEAD');
|
|
91
|
+
const lastCommitHash = git('rev-parse --short HEAD');
|
|
92
|
+
const lastCommitMessage = git('log -1 --format=%s');
|
|
93
|
+
const lastCommitAuthor = git('log -1 --format=%an');
|
|
94
|
+
const lastCommitDate = git('log -1 --format=%ci');
|
|
95
|
+
const remoteBranch = git(`rev-parse --abbrev-ref ${branch}@{upstream} 2>/dev/null`) || null;
|
|
96
|
+
|
|
97
|
+
let ahead = 0;
|
|
98
|
+
let behind = 0;
|
|
99
|
+
if (remoteBranch) {
|
|
100
|
+
const aheadBehind = git(`rev-list --left-right --count ${branch}...${remoteBranch}`);
|
|
101
|
+
if (aheadBehind) {
|
|
102
|
+
const [a, b] = aheadBehind.split('\t').map(Number);
|
|
103
|
+
ahead = a || 0;
|
|
104
|
+
behind = b || 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const statusOutput = git('status --porcelain') || '';
|
|
109
|
+
const files = {
|
|
110
|
+
staged: [],
|
|
111
|
+
modified: [],
|
|
112
|
+
untracked: [],
|
|
113
|
+
deleted: [],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
statusOutput.split('\n').filter(Boolean).forEach(line => {
|
|
117
|
+
const indexStatus = line[0];
|
|
118
|
+
const workTreeStatus = line[1];
|
|
119
|
+
const filePath = line.substring(3);
|
|
120
|
+
|
|
121
|
+
if (indexStatus !== ' ' && indexStatus !== '?') {
|
|
122
|
+
if (indexStatus === 'D') {
|
|
123
|
+
files.deleted.push(filePath);
|
|
124
|
+
} else {
|
|
125
|
+
files.staged.push(filePath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (workTreeStatus === 'M') {
|
|
130
|
+
files.modified.push(filePath);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (indexStatus === '?' && workTreeStatus === '?') {
|
|
134
|
+
files.untracked.push(filePath);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (workTreeStatus === 'D') {
|
|
138
|
+
files.deleted.push(filePath);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const stashList = git('stash list') || '';
|
|
143
|
+
const stashCount = stashList ? stashList.split('\n').filter(Boolean).length : 0;
|
|
144
|
+
const remoteUrl = git('remote get-url origin') || null;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
branch,
|
|
148
|
+
remote_branch: remoteBranch,
|
|
149
|
+
remote_url: remoteUrl,
|
|
150
|
+
last_commit: {
|
|
151
|
+
hash: lastCommitHash,
|
|
152
|
+
message: lastCommitMessage,
|
|
153
|
+
author: lastCommitAuthor,
|
|
154
|
+
date: lastCommitDate,
|
|
155
|
+
},
|
|
156
|
+
ahead_of_remote: ahead,
|
|
157
|
+
behind_remote: behind,
|
|
158
|
+
files,
|
|
159
|
+
stash_count: stashCount,
|
|
160
|
+
has_changes: files.staged.length > 0 || files.modified.length > 0 || files.untracked.length > 0,
|
|
161
|
+
total_changes: files.staged.length + files.modified.length + files.untracked.length + files.deleted.length,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Envía el estado a la plataforma
|
|
167
|
+
*/
|
|
168
|
+
async function reportStatus(status) {
|
|
169
|
+
const payload = {
|
|
170
|
+
machine_id: CONFIG.machineId,
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
status,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const headers = {
|
|
176
|
+
'Content-Type': 'application/json',
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (CONFIG.authToken) {
|
|
180
|
+
headers['Authorization'] = `Bearer ${CONFIG.authToken}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const response = await fetch(CONFIG.apiUrl, {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers,
|
|
186
|
+
body: JSON.stringify(payload),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
const error = await response.text();
|
|
191
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return await response.json();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Muestra el estado en consola
|
|
199
|
+
*/
|
|
200
|
+
function printStatus(status) {
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(`${colors.blue}═══════════════════════════════════════════════════════${colors.reset}`);
|
|
203
|
+
console.log(`${colors.blue} DGP Agent Status${colors.reset}`);
|
|
204
|
+
console.log(`${colors.blue}═══════════════════════════════════════════════════════${colors.reset}`);
|
|
205
|
+
console.log(` Machine: ${colors.yellow}${CONFIG.machineId}${colors.reset}`);
|
|
206
|
+
console.log(` Branch: ${colors.green}${status.branch}${colors.reset}`);
|
|
207
|
+
console.log(` Commit: ${status.last_commit.hash} - ${status.last_commit.message.substring(0, 40)}...`);
|
|
208
|
+
|
|
209
|
+
if (status.ahead_of_remote > 0 || status.behind_of_remote > 0) {
|
|
210
|
+
console.log(` Remote: ${colors.yellow}↑${status.ahead_of_remote} ↓${status.behind_remote}${colors.reset}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log(` ${colors.gray}Changes:${colors.reset}`);
|
|
215
|
+
console.log(` Staged: ${status.files.staged.length}`);
|
|
216
|
+
console.log(` Modified: ${status.files.modified.length}`);
|
|
217
|
+
console.log(` Untracked: ${status.files.untracked.length}`);
|
|
218
|
+
console.log(` Deleted: ${status.files.deleted.length}`);
|
|
219
|
+
console.log(`${colors.blue}═══════════════════════════════════════════════════════${colors.reset}`);
|
|
220
|
+
console.log('');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Loop principal del agente
|
|
225
|
+
*/
|
|
226
|
+
async function runAgent() {
|
|
227
|
+
console.log('');
|
|
228
|
+
console.log(`${colors.green}╔═══════════════════════════════════════════════════════╗${colors.reset}`);
|
|
229
|
+
console.log(`${colors.green}║ DGP Agent - Despliegue-GPT Local Agent ║${colors.reset}`);
|
|
230
|
+
console.log(`${colors.green}║ @deinos/dgp-agent v1.0.0 ║${colors.reset}`);
|
|
231
|
+
console.log(`${colors.green}╚═══════════════════════════════════════════════════════╝${colors.reset}`);
|
|
232
|
+
console.log('');
|
|
233
|
+
|
|
234
|
+
if (!isGitRepo()) {
|
|
235
|
+
logError('Not a git repository. Run this command from inside a git repo.');
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
logInfo(`Machine ID: ${CONFIG.machineId}`);
|
|
240
|
+
logInfo(`Report interval: ${CONFIG.interval}s`);
|
|
241
|
+
logInfo(`API URL: ${CONFIG.apiUrl}`);
|
|
242
|
+
|
|
243
|
+
if (!CONFIG.authToken) {
|
|
244
|
+
log('INFO: No auth token. Reporting anonymously.', 'yellow');
|
|
245
|
+
log('Set DGP_AUTH_TOKEN to associate with your user.', 'gray');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log('');
|
|
249
|
+
logInfo('Press Ctrl+C to stop');
|
|
250
|
+
console.log('');
|
|
251
|
+
|
|
252
|
+
const runCycle = async () => {
|
|
253
|
+
try {
|
|
254
|
+
const status = getRepoStatus();
|
|
255
|
+
printStatus(status);
|
|
256
|
+
|
|
257
|
+
logInfo('Reporting to platform...');
|
|
258
|
+
const result = await reportStatus(status);
|
|
259
|
+
logSuccess(`Reported successfully. Session: ${result.session_id || 'N/A'}`);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logError(error.message);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
await runCycle();
|
|
266
|
+
setInterval(runCycle, CONFIG.interval * 1000);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Comando único para ver el estado sin loop
|
|
271
|
+
*/
|
|
272
|
+
async function showStatus() {
|
|
273
|
+
if (!isGitRepo()) {
|
|
274
|
+
logError('Not a git repository. Run this command from inside a git repo.');
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const status = getRepoStatus();
|
|
279
|
+
printStatus(status);
|
|
280
|
+
|
|
281
|
+
if (status.has_changes) {
|
|
282
|
+
console.log(`${colors.gray}Files with changes:${colors.reset}`);
|
|
283
|
+
|
|
284
|
+
if (status.files.staged.length > 0) {
|
|
285
|
+
console.log(`\n${colors.green}Staged:${colors.reset}`);
|
|
286
|
+
status.files.staged.forEach(f => console.log(` ${colors.green}+ ${f}${colors.reset}`));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (status.files.modified.length > 0) {
|
|
290
|
+
console.log(`\n${colors.yellow}Modified:${colors.reset}`);
|
|
291
|
+
status.files.modified.forEach(f => console.log(` ${colors.yellow}M ${f}${colors.reset}`));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (status.files.untracked.length > 0) {
|
|
295
|
+
console.log(`\n${colors.gray}Untracked:${colors.reset}`);
|
|
296
|
+
status.files.untracked.forEach(f => console.log(` ${colors.gray}? ${f}${colors.reset}`));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (status.files.deleted.length > 0) {
|
|
300
|
+
console.log(`\n${colors.red}Deleted:${colors.reset}`);
|
|
301
|
+
status.files.deleted.forEach(f => console.log(` ${colors.red}- ${f}${colors.reset}`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Muestra ayuda
|
|
308
|
+
*/
|
|
309
|
+
function showHelp() {
|
|
310
|
+
console.log(`
|
|
311
|
+
${colors.bold}${colors.cyan}DGP Agent - Despliegue-GPT Local Agent${colors.reset}
|
|
312
|
+
${colors.gray}@deinos/dgp-agent v1.0.0${colors.reset}
|
|
313
|
+
|
|
314
|
+
${colors.bold}DESCRIPCIÓN${colors.reset}
|
|
315
|
+
Agente local que reporta el estado de tu repositorio Git
|
|
316
|
+
a la plataforma TenMinute IA (Despliegue-GPT).
|
|
317
|
+
|
|
318
|
+
${colors.bold}INSTALACIÓN${colors.reset}
|
|
319
|
+
${colors.green}npm install -g @deinos/dgp-agent${colors.reset}
|
|
320
|
+
|
|
321
|
+
${colors.bold}USO${colors.reset}
|
|
322
|
+
${colors.cyan}dgp-agent${colors.reset} Inicia el agente (reporta cada 30s)
|
|
323
|
+
${colors.cyan}dgp-agent status${colors.reset} Muestra el estado actual una vez
|
|
324
|
+
${colors.cyan}dgp-agent help${colors.reset} Muestra esta ayuda
|
|
325
|
+
|
|
326
|
+
${colors.bold}VARIABLES DE ENTORNO${colors.reset}
|
|
327
|
+
${colors.yellow}DGP_AUTH_TOKEN${colors.reset} Token JWT para asociar a tu usuario
|
|
328
|
+
(Obtenerlo desde la plataforma)
|
|
329
|
+
${colors.yellow}DGP_API_URL${colors.reset} URL del endpoint API
|
|
330
|
+
${colors.yellow}DGP_INTERVAL${colors.reset} Intervalo de reporte en segundos (default: 30)
|
|
331
|
+
${colors.yellow}DGP_MACHINE_ID${colors.reset} ID personalizado de la máquina
|
|
332
|
+
|
|
333
|
+
${colors.bold}EJEMPLOS${colors.reset}
|
|
334
|
+
# Iniciar agente básico
|
|
335
|
+
${colors.gray}$ dgp-agent${colors.reset}
|
|
336
|
+
|
|
337
|
+
# Con token de autenticación
|
|
338
|
+
${colors.gray}$ DGP_AUTH_TOKEN=eyJ... dgp-agent${colors.reset}
|
|
339
|
+
|
|
340
|
+
# Intervalo personalizado (60 segundos)
|
|
341
|
+
${colors.gray}$ DGP_INTERVAL=60 dgp-agent${colors.reset}
|
|
342
|
+
|
|
343
|
+
${colors.bold}MÁS INFO${colors.reset}
|
|
344
|
+
https://github.com/DEINOS-SRL/tenminuteia
|
|
345
|
+
`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// CLI
|
|
349
|
+
const args = process.argv.slice(2);
|
|
350
|
+
const command = args[0];
|
|
351
|
+
|
|
352
|
+
switch (command) {
|
|
353
|
+
case 'status':
|
|
354
|
+
case '-s':
|
|
355
|
+
case '--status':
|
|
356
|
+
showStatus();
|
|
357
|
+
break;
|
|
358
|
+
case 'help':
|
|
359
|
+
case '-h':
|
|
360
|
+
case '--help':
|
|
361
|
+
showHelp();
|
|
362
|
+
break;
|
|
363
|
+
case 'version':
|
|
364
|
+
case '-v':
|
|
365
|
+
case '--version':
|
|
366
|
+
console.log('@deinos/dgp-agent v1.0.0');
|
|
367
|
+
break;
|
|
368
|
+
default:
|
|
369
|
+
runAgent();
|
|
370
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deinossrl/dgp-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Agente local para Despliegue-GPT - Reporta el estado del repositorio Git a la plataforma TenMinute IA",
|
|
5
|
+
"main": "index.mjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dgp-agent": "./index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.mjs",
|
|
12
|
+
"status": "node index.mjs status"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"git",
|
|
16
|
+
"deploy",
|
|
17
|
+
"devops",
|
|
18
|
+
"agent",
|
|
19
|
+
"tenminute",
|
|
20
|
+
"dgp"
|
|
21
|
+
],
|
|
22
|
+
"author": "DEINOS SRL",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/DEINOS-SRL/tenminuteia.git",
|
|
27
|
+
"directory": "packages/dgp-agent"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/DEINOS-SRL/tenminuteia#readme",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|