@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.
Files changed (2) hide show
  1. package/index.mjs +370 -0
  2. 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
+ }