plazbot-cli 0.2.2 → 0.2.4

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 (43) hide show
  1. package/dist/commands/agent/chat.js +1 -1
  2. package/dist/commands/agent/delete.js +1 -1
  3. package/dist/commands/agent/enable-widget.js +7 -11
  4. package/dist/commands/agent/get.js +1 -1
  5. package/dist/commands/agent/index.js +3 -1
  6. package/dist/commands/agent/list.js +1 -1
  7. package/dist/commands/agent/monitor.js +293 -0
  8. package/dist/commands/agent/on-message.js +10 -12
  9. package/dist/commands/agent/update.js +1 -1
  10. package/dist/commands/auth/login.js +13 -12
  11. package/dist/commands/portal/add-agent.js +5 -10
  12. package/dist/commands/portal/add-link.js +3 -2
  13. package/dist/commands/portal/clear-links.js +3 -2
  14. package/dist/commands/portal/create.js +3 -7
  15. package/dist/commands/portal/delete.js +5 -6
  16. package/dist/commands/portal/get.js +15 -17
  17. package/dist/commands/portal/list.js +15 -17
  18. package/dist/commands/portal/update.js +5 -6
  19. package/dist/commands/whatsapp/delete-webhook.js +3 -3
  20. package/dist/commands/whatsapp/register-webhook.js +4 -4
  21. package/dist/utils/logger.js +3 -3
  22. package/package.json +2 -1
  23. package/src/commands/agent/chat.ts +1 -1
  24. package/src/commands/agent/delete.ts +1 -1
  25. package/src/commands/agent/enable-widget.ts +8 -12
  26. package/src/commands/agent/get.ts +1 -1
  27. package/src/commands/agent/index.ts +3 -1
  28. package/src/commands/agent/list.ts +1 -1
  29. package/src/commands/agent/monitor.ts +346 -0
  30. package/src/commands/agent/on-message.ts +11 -13
  31. package/src/commands/agent/update.ts +1 -1
  32. package/src/commands/auth/login.ts +13 -12
  33. package/src/commands/portal/add-agent.ts +5 -11
  34. package/src/commands/portal/add-link.ts +2 -2
  35. package/src/commands/portal/clear-links.ts +3 -2
  36. package/src/commands/portal/create.ts +3 -8
  37. package/src/commands/portal/delete.ts +5 -6
  38. package/src/commands/portal/get.ts +16 -20
  39. package/src/commands/portal/list.ts +16 -19
  40. package/src/commands/portal/update.ts +5 -6
  41. package/src/commands/whatsapp/delete-webhook.ts +3 -3
  42. package/src/commands/whatsapp/register-webhook.ts +4 -4
  43. package/src/utils/logger.ts +3 -3
@@ -19,39 +19,37 @@ exports.getCommand = new commander_1.Command('get')
19
19
  zone: credentials.zone,
20
20
  ...(options.dev && { customUrl: "http://localhost:5090" })
21
21
  });
22
- logger_1.logger.info('\n🔄 Obteniendo detalles del portal...');
23
22
  const response = await portal.getPortal(portalId);
24
23
  const portalDetails = response.portal;
25
- logger_1.logger.info('\n🌐 Detalles del portal:');
26
- logger_1.logger.divider();
27
- logger_1.logger.info(`ID: ${portalDetails.id}`);
28
- logger_1.logger.info(`Nombre: ${portalDetails.name}`);
29
- logger_1.logger.info(`URL: ${portalDetails.url}`);
30
- logger_1.logger.info(`Acceso: ${portalDetails.access}`);
31
- logger_1.logger.info(`Estado: ${portalDetails.disabled ? 'Deshabilitado' : 'Habilitado'}`);
24
+ logger_1.logger.title('Detalles del portal');
25
+ logger_1.logger.label('ID', portalDetails.id);
26
+ logger_1.logger.label('Nombre', portalDetails.name);
27
+ logger_1.logger.label('URL', portalDetails.url);
28
+ logger_1.logger.label('Acceso', portalDetails.access);
29
+ logger_1.logger.label('Estado', portalDetails.disabled ? 'Deshabilitado' : 'Habilitado');
32
30
  if (portalDetails.title) {
33
- logger_1.logger.info(`Título: ${portalDetails.title}`);
31
+ logger_1.logger.label('Titulo', portalDetails.title);
34
32
  }
35
33
  if (portalDetails.subtitle) {
36
- logger_1.logger.info(`Subtítulo: ${portalDetails.subtitle}`);
34
+ logger_1.logger.label('Subtitulo', portalDetails.subtitle);
37
35
  }
38
36
  if (portalDetails.logo) {
39
- logger_1.logger.info(`\n🖼️ Recursos:`);
40
- logger_1.logger.info(`Logo: ${portalDetails.logo}`);
37
+ logger_1.logger.title('Recursos');
38
+ logger_1.logger.label('Logo', portalDetails.logo);
41
39
  if (portalDetails.logodark) {
42
- logger_1.logger.info(`Logo (modo oscuro): ${portalDetails.logodark}`);
40
+ logger_1.logger.label('Logo (modo oscuro)', portalDetails.logodark);
43
41
  }
44
42
  }
45
43
  if (portalDetails.links && portalDetails.links.length > 0) {
46
- logger_1.logger.info('\n🔗 Enlaces:');
44
+ logger_1.logger.title('Enlaces');
47
45
  portalDetails.links.forEach((link) => {
48
- logger_1.logger.info(`- ${link.value}: ${link.url}`);
46
+ logger_1.logger.label(link.value, link.url);
49
47
  });
50
48
  }
51
49
  if (portalDetails.agents && portalDetails.agents.length > 0) {
52
- logger_1.logger.info('\n🤖 Agentes asociados:');
50
+ logger_1.logger.title('Agentes asociados');
53
51
  portalDetails.agents.forEach((agent) => {
54
- logger_1.logger.info(`- ${agent.name} (${agent.id})`);
52
+ logger_1.logger.label(agent.name, agent.id);
55
53
  });
56
54
  }
57
55
  if (options.dev) {
@@ -18,40 +18,38 @@ exports.listCommand = new commander_1.Command('list')
18
18
  zone: credentials.zone,
19
19
  ...(options.dev && { customUrl: "http://localhost:5090" })
20
20
  });
21
- logger_1.logger.info('\n🔍 Buscando portal del workspace...');
22
21
  const response = await portal.getExistsPortal();
23
22
  const portalDetails = response.portal;
24
- logger_1.logger.info('\n🌐 Portal encontrado:');
25
- logger_1.logger.divider();
26
- logger_1.logger.info(`ID: ${portalDetails.id}`);
27
- logger_1.logger.info(`Nombre: ${portalDetails.name}`);
28
- logger_1.logger.info(`URL: ${portalDetails.url}`);
29
- logger_1.logger.info(`Acceso: ${portalDetails.access}`);
30
- logger_1.logger.info(`Estado: ${portalDetails.disabled ? 'Deshabilitado' : 'Habilitado'}`);
23
+ logger_1.logger.title('Portal encontrado');
24
+ logger_1.logger.label('ID', portalDetails.id);
25
+ logger_1.logger.label('Nombre', portalDetails.name);
26
+ logger_1.logger.label('URL', portalDetails.url);
27
+ logger_1.logger.label('Acceso', portalDetails.access || 'N/A');
28
+ logger_1.logger.label('Estado', portalDetails.disabled ? 'Deshabilitado' : 'Habilitado');
31
29
  if (portalDetails.title) {
32
- logger_1.logger.info(`Título: ${portalDetails.title}`);
30
+ logger_1.logger.label('Titulo', portalDetails.title);
33
31
  }
34
32
  if (portalDetails.subtitle) {
35
- logger_1.logger.info(`Subtítulo: ${portalDetails.subtitle}`);
33
+ logger_1.logger.label('Subtitulo', portalDetails.subtitle);
36
34
  }
37
35
  if (portalDetails.logo) {
38
- logger_1.logger.info('\n🖼️ Recursos:');
39
- logger_1.logger.info(`Logo: ${portalDetails.logo}`);
36
+ logger_1.logger.title('Recursos');
37
+ logger_1.logger.label('Logo', portalDetails.logo);
40
38
  if (portalDetails.logodark) {
41
- logger_1.logger.info(`Logo (modo oscuro): ${portalDetails.logodark}`);
39
+ logger_1.logger.label('Logo (modo oscuro)', portalDetails.logodark);
42
40
  }
43
41
  }
44
42
  const portalAny = portalDetails;
45
43
  if (portalAny.links && portalAny.links.length > 0) {
46
- logger_1.logger.info('\n🔗 Enlaces:');
44
+ logger_1.logger.title('Enlaces');
47
45
  portalAny.links.forEach((link) => {
48
- logger_1.logger.info(`- ${link.value}: ${link.url}`);
46
+ logger_1.logger.label(link.value, link.url);
49
47
  });
50
48
  }
51
49
  if (portalAny.agents && portalAny.agents.length > 0) {
52
- logger_1.logger.info('\n🤖 Agentes asociados:');
50
+ logger_1.logger.title('Agentes asociados');
53
51
  portalAny.agents.forEach((agent) => {
54
- logger_1.logger.info(`- ${agent.name} (${agent.id})`);
52
+ logger_1.logger.label(agent.name, agent.id);
55
53
  });
56
54
  }
57
55
  if (options.dev) {
@@ -29,9 +29,8 @@ exports.updateCommand = new commander_1.Command('update')
29
29
  });
30
30
  // Obtener detalles actuales del portal
31
31
  const currentPortal = await portal.getPortal(portalId);
32
- logger_1.logger.info('\n🌐 Actualizando portal...');
33
- logger_1.logger.info('Portal actual:');
34
- logger_1.logger.info(JSON.stringify(currentPortal.portal, null, 2));
32
+ logger_1.logger.title('Portal actual');
33
+ logger_1.logger.json(currentPortal.portal);
35
34
  // Construir objeto de actualización solo con los campos proporcionados
36
35
  const updateData = { id: portalId };
37
36
  if (options.name)
@@ -62,11 +61,11 @@ exports.updateCommand = new commander_1.Command('update')
62
61
  }
63
62
  updateData.brandOff = options.brandOff === 'true';
64
63
  }
65
- logger_1.logger.info('\nCambios a aplicar:');
66
- logger_1.logger.info(JSON.stringify(updateData, null, 2));
64
+ logger_1.logger.title('Cambios a aplicar');
65
+ logger_1.logger.json(updateData);
67
66
  const result = await portal.updatePortal(updateData);
68
67
  logger_1.logger.success('Portal actualizado exitosamente');
69
- logger_1.logger.info(`Mensaje: ${result.message}`);
68
+ logger_1.logger.label('Mensaje', result.message);
70
69
  if (options.dev) {
71
70
  logger_1.logger.warning('\nAmbiente: desarrollo');
72
71
  }
@@ -18,15 +18,15 @@ exports.deleteWebhookCommand = new commander_1.Command('delete-webhook')
18
18
  zone: credentials.zone,
19
19
  ...(options.dev && { customUrl: "http://localhost:5090" })
20
20
  });
21
- logger_1.logger.info('\n🗑️ Eliminando webhook de WhatsApp...');
22
- logger_1.logger.info(`Número: ${options.number}`);
21
+ logger_1.logger.title('Eliminando webhook de WhatsApp');
22
+ logger_1.logger.label('Numero', options.number);
23
23
  await messageClient.deleteWebhook({
24
24
  number: options.number
25
25
  });
26
26
  logger_1.logger.success('Webhook eliminado exitosamente');
27
27
  }
28
28
  catch (error) {
29
- logger_1.logger.error(`❌ Error al eliminar webhook: ${error.message}`);
29
+ logger_1.logger.error(error.message);
30
30
  process.exit(1);
31
31
  }
32
32
  });
@@ -19,9 +19,9 @@ exports.registerWebhookCommand = new commander_1.Command('register-webhook')
19
19
  zone: credentials.zone,
20
20
  ...(options.dev && { customUrl: "http://localhost:5090" })
21
21
  });
22
- logger_1.logger.info('\n🔗 Registrando webhook de WhatsApp...');
23
- logger_1.logger.info(`Número: ${options.number}`);
24
- logger_1.logger.info(`URL: ${options.url}`);
22
+ logger_1.logger.title('Registrando webhook de WhatsApp');
23
+ logger_1.logger.label('Numero', options.number);
24
+ logger_1.logger.label('URL', options.url);
25
25
  await messageClient.registerWebhook({
26
26
  number: options.number,
27
27
  webhookUrl: options.url
@@ -29,7 +29,7 @@ exports.registerWebhookCommand = new commander_1.Command('register-webhook')
29
29
  logger_1.logger.success('Webhook registrado exitosamente');
30
30
  }
31
31
  catch (error) {
32
- logger_1.logger.error(`❌ Error al registrar webhook: ${error.message}`);
32
+ logger_1.logger.error(error.message);
33
33
  process.exit(1);
34
34
  }
35
35
  });
@@ -10,14 +10,14 @@ exports.logger = {
10
10
  console.log(chalk_1.default.white(message));
11
11
  },
12
12
  success: (message) => {
13
- console.log(chalk_1.default.hex('#66BB6A')(`\n ${message}`));
13
+ console.log(chalk_1.default.hex('#66BB6A')(`\n ${message}`));
14
14
  },
15
15
  warning: (message) => {
16
- console.log(chalk_1.default.hex('#FFA726')(`\n ${message}`));
16
+ console.log(chalk_1.default.hex('#FFA726')(`\n ${message}`));
17
17
  },
18
18
  error: (error) => {
19
19
  const message = error instanceof Error ? error.message : error;
20
- console.error(chalk_1.default.hex('#EF5350')(`\n Error: ${message}`));
20
+ console.error(chalk_1.default.hex('#EF5350')(`\n Error: ${message}`));
21
21
  },
22
22
  divider: (length = 50) => {
23
23
  console.log(chalk_1.default.gray('─'.repeat(length)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plazbot-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "CLI para Plazbot SDK",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -31,6 +31,7 @@
31
31
  "author": "Kristian Garcia <kristian@plazbot.com>",
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
+ "@microsoft/signalr": "^10.0.0",
34
35
  "@types/inquirer": "^9.0.8",
35
36
  "axios": "^1.6.0",
36
37
  "boxen": "^5.1.2",
@@ -138,7 +138,7 @@ export const chatCommand = new Command('chat')
138
138
  // Mostrar tool calls si hay
139
139
  if (response.actionsExecuted && response.actionsExecuted.length > 0) {
140
140
  response.actionsExecuted.forEach((action: any) => {
141
- console.log(chalk.hex('#FFA726')(` Tool: ${action.name || action.intent || 'action'}`));
141
+ console.log(chalk.hex('#FFA726')(` Tool: ${action.name || action.intent || 'action'}`));
142
142
  });
143
143
  }
144
144
 
@@ -38,7 +38,7 @@ export const deleteCommand = new Command('delete')
38
38
  });
39
39
 
40
40
  // Preguntar por confirmación
41
- rl.question('\n❗ ¿Estás seguro que deseas eliminar este agente? (y/N): ', async (answer) => {
41
+ rl.question('\nEstas seguro que deseas eliminar este agente? (y/N): ', async (answer) => {
42
42
  if (answer.toLowerCase() === 'y') {
43
43
  await agent.deleteAgent({
44
44
  id: agentId
@@ -24,10 +24,8 @@ export const enableCommand = new Command('enable-widget')
24
24
  // Obtener estado actual del agente
25
25
  const agentDetails = await agent.getAgentById({ id: agentId });
26
26
 
27
- logger.info('\n🔧 Estado actual del widget:');
28
- logger.divider();
29
- logger.info(`Agente: ${agentDetails.name}`);
30
- logger.divider();
27
+ logger.title('Estado actual del widget');
28
+ logger.label('Agente', agentDetails.name);
31
29
 
32
30
  // Cambiar estado
33
31
  const newState = !options.disable; // Si --disable está presente, newState será false
@@ -38,15 +36,13 @@ export const enableCommand = new Command('enable-widget')
38
36
 
39
37
  logger.success(`Widget ${newState ? 'habilitado' : 'deshabilitado'} exitosamente`);
40
38
 
41
- logger.info('\n🔧 Respuesta del servidor:');
42
- logger.divider();
43
- logger.info(`Estado: ${result.success ? '✅ Exitoso' : '❌ Fallido'}`);
44
- logger.info(`Mensaje: ${result.message}`);
45
-
39
+ logger.title('Respuesta del servidor');
40
+ logger.label('Estado', result.success ? 'Exitoso' : 'Fallido');
41
+ logger.label('Mensaje', result.message);
42
+
46
43
  if (newState && result.script) {
47
- logger.info('\n📝 Instrucciones de instalación:');
48
- logger.divider();
49
- logger.info('Coloca este script debajo de la etiqueta <HEAD> en tu sitio web:');
44
+ logger.title('Instrucciones de instalacion');
45
+ logger.dim('Coloca este script debajo de la etiqueta <HEAD> en tu sitio web:');
50
46
  logger.info('\n' + result.script);
51
47
  }
52
48
 
@@ -33,7 +33,7 @@ export const getCommand = new Command('get')
33
33
  logger.info(`ID: ${agentData.id}`);
34
34
  logger.info(`Nombre: ${agentData.name}`);
35
35
  logger.info(`Descripción: ${agentData.description}`);
36
- logger.info(`Estado: ${agentData.enable ? 'Activo' : 'Inactivo'}`);
36
+ logger.info(`Estado: ${agentData.enable ? 'Activo' : 'Inactivo'}`);
37
37
  logger.info(`Zona: ${agentData.zone}`);
38
38
  logger.info(`Buffer: ${agentData.buffer}`);
39
39
  logger.info(`Color: ${agentData.color}`);
@@ -13,6 +13,7 @@ import { templatesCommand } from './templates';
13
13
  import { copyCommand } from './copy';
14
14
  import { filesCommand } from './files';
15
15
  import { setCommand } from './set';
16
+ import { monitorCommand } from './monitor';
16
17
 
17
18
  export const agentCommands = new Command('agent')
18
19
  .description('Comandos relacionados con agentes de IA')
@@ -29,4 +30,5 @@ export const agentCommands = new Command('agent')
29
30
  .addCommand(templatesCommand)
30
31
  .addCommand(copyCommand)
31
32
  .addCommand(filesCommand)
32
- .addCommand(setCommand);
33
+ .addCommand(setCommand)
34
+ .addCommand(monitorCommand);
@@ -34,7 +34,7 @@ export const listCommand = new Command('list')
34
34
  agents.forEach((a: any, index: number) => {
35
35
  logger.info(`${index + 1}. ID: ${a.id}`);
36
36
  logger.info(` Nombre: ${a.name}`);
37
- logger.info(` Estado: ${a.enable ? 'Activo' : 'Inactivo'}`);
37
+ logger.info(` Estado: ${a.enable ? 'Activo' : 'Inactivo'}`);
38
38
  logger.info(` Descripción: ${a.description}`);
39
39
  logger.info(` Creado: ${a.createdAt ? new Date(a.createdAt).toLocaleString() : 'N/A'}`);
40
40
  logger.divider();
@@ -0,0 +1,346 @@
1
+ import { Command } from 'commander';
2
+ import { Agent } from 'plazbot';
3
+ import { getStoredCredentials } from '../../utils/credentials';
4
+ import { logger } from '../../utils/logger';
5
+ import { theme } from '../../utils/ui';
6
+ import chalk from 'chalk';
7
+ import readline from 'readline';
8
+ import { HubConnectionBuilder, HttpTransportType, HubConnectionState } from '@microsoft/signalr';
9
+
10
+ // Colores por tipo de evento (mismos del MonitorTab frontend)
11
+ const EVENT_COLORS: Record<string, { color: string; label: string }> = {
12
+ msg_in: { color: '#22d3ee', label: 'msg_in' },
13
+ msg_out: { color: '#4ade80', label: 'msg_out' },
14
+ tool_call: { color: '#c084fc', label: 'tool_call' },
15
+ api_req: { color: '#fbbf24', label: 'api_req' },
16
+ api_res: { color: '#fbbf24', label: 'api_res' },
17
+ action: { color: '#f97316', label: 'action' },
18
+ rag: { color: '#60a5fa', label: 'rag' },
19
+ tokens: { color: '#94a3b8', label: 'tokens' },
20
+ error: { color: '#ef4444', label: 'error' },
21
+ model: { color: '#a78bfa', label: 'model' },
22
+ intent: { color: '#a78bfa', label: 'intent' },
23
+ bot_off: { color: '#f97316', label: 'bot_off' },
24
+ session_field: { color: '#a78bfa', label: 'session_field' },
25
+ };
26
+
27
+ const DEFAULT_EVENT_COLOR = { color: '#94a3b8', label: 'unknown' };
28
+
29
+ interface AgentLogEvent {
30
+ timestamp: string;
31
+ workspace_id: string;
32
+ agent_id: string;
33
+ contact_id: string;
34
+ contact_name: string;
35
+ session_id: string;
36
+ event_type: string;
37
+ event_title: string;
38
+ event_data: string;
39
+ duration_ms: number;
40
+ level: string;
41
+ }
42
+
43
+ function getBaseUrl(zone: string, dev: boolean): string {
44
+ if (dev) return 'http://localhost:5090';
45
+ return zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com';
46
+ }
47
+
48
+ function formatTimestamp(ts: string): string {
49
+ try {
50
+ const d = new Date(ts);
51
+ return d.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
52
+ } catch {
53
+ return ts.substring(11, 19);
54
+ }
55
+ }
56
+
57
+ function truncate(text: string, maxLen: number): string {
58
+ if (!text) return '';
59
+ return text.length > maxLen ? text.substring(0, maxLen - 3) + '...' : text;
60
+ }
61
+
62
+ function shortSession(sessionId: string): string {
63
+ if (!sessionId) return '';
64
+ return sessionId.substring(0, 6);
65
+ }
66
+
67
+ function renderLogLine(
68
+ log: AgentLogEvent,
69
+ opts: { showSession: boolean; jsonMode: boolean },
70
+ lastSessionId: string
71
+ ): string {
72
+ // Modo JSON crudo
73
+ if (opts.jsonMode) {
74
+ return JSON.stringify(log);
75
+ }
76
+
77
+ const config = EVENT_COLORS[log.event_type] || DEFAULT_EVENT_COLOR;
78
+ const colorFn = chalk.hex(config.color);
79
+ const grayFn = chalk.hex('#71717a');
80
+ const dimFn = chalk.hex('#52525b');
81
+
82
+ let lines: string[] = [];
83
+
84
+ // Separador de sesion
85
+ if (lastSessionId && log.session_id && log.session_id !== lastSessionId) {
86
+ lines.push(dimFn(' ' + '─'.repeat(16) + ' nueva sesion ' + '─'.repeat(16)));
87
+ }
88
+
89
+ // Linea principal
90
+ const timestamp = grayFn(formatTimestamp(log.timestamp));
91
+ const dot = colorFn('●');
92
+ const eventType = colorFn(config.label.padEnd(14));
93
+ const title = chalk.white(truncate(log.event_title, 80));
94
+ const duration = log.duration_ms > 0 ? dimFn(` ${log.duration_ms}ms`) : '';
95
+ const session = opts.showSession && log.session_id
96
+ ? dimFn(` [${shortSession(log.session_id)}]`)
97
+ : '';
98
+
99
+ lines.push(` ${timestamp} ${dot} ${eventType} ${title}${duration}${session}`);
100
+
101
+ // Linea de contacto para mensajes
102
+ if (log.contact_name && (log.event_type === 'msg_in' || log.event_type === 'msg_out')) {
103
+ lines.push(dimFn(' | contacto: ') + dimFn(log.contact_name));
104
+ }
105
+
106
+ return lines.join('\n');
107
+ }
108
+
109
+ function printHeader(agentName: string) {
110
+ console.log();
111
+ console.log(chalk.hex('#4CAF50')(' ┌' + '─'.repeat(62) + '┐'));
112
+ console.log(
113
+ chalk.hex('#4CAF50')(' │') +
114
+ chalk.bold(` Monitor`) +
115
+ chalk.hex('#4ade80')(' ● En vivo') +
116
+ ' '.repeat(38) +
117
+ chalk.hex('#4CAF50')('│')
118
+ );
119
+ console.log(
120
+ chalk.hex('#4CAF50')(' │') +
121
+ chalk.gray(` Agente: ${truncate(agentName, 44)}`) +
122
+ ' '.repeat(Math.max(0, 49 - agentName.length)) +
123
+ chalk.hex('#4CAF50')('│')
124
+ );
125
+ console.log(
126
+ chalk.hex('#4CAF50')(' │') +
127
+ chalk.gray(' Ctrl+C salir | /filter <tipo> | /clear | /help') +
128
+ ' '.repeat(12) +
129
+ chalk.hex('#4CAF50')('│')
130
+ );
131
+ console.log(chalk.hex('#4CAF50')(' └' + '─'.repeat(62) + '┘'));
132
+ console.log();
133
+ }
134
+
135
+ const COMMANDS_HELP = `
136
+ ${chalk.bold('Comandos disponibles:')}
137
+ ${chalk.hex('#4CAF50')('/filter <tipo>')} Toggle filtro por tipo (ej: msg_in, error, tool_call)
138
+ ${chalk.hex('#4CAF50')('/filters')} Mostrar filtros activos
139
+ ${chalk.hex('#4CAF50')('/clear')} Limpiar pantalla
140
+ ${chalk.hex('#4CAF50')('/json')} Toggle modo JSON expandido
141
+ ${chalk.hex('#4CAF50')('/help')} Mostrar estos comandos
142
+
143
+ ${chalk.bold('Tipos de evento:')}
144
+ ${Object.entries(EVENT_COLORS).map(([key, val]) =>
145
+ ` ${chalk.hex(val.color)('●')} ${key}`
146
+ ).join('\n')}
147
+ `;
148
+
149
+ export const monitorCommand = new Command('monitor')
150
+ .description('Monitor en tiempo real de un agente (logs via SignalR)')
151
+ .requiredOption('-a, --agent-id <id>', 'ID del agente')
152
+ .option('-f, --filter <types>', 'Filtrar por tipos separados por coma (ej: msg_in,msg_out)')
153
+ .option('--no-session', 'Ocultar session ID')
154
+ .option('--json', 'Modo JSON crudo (para piping)', false)
155
+ .option('--dev', 'Usar ambiente de desarrollo', false)
156
+ .action(async (options: {
157
+ agentId: string;
158
+ filter?: string;
159
+ session: boolean;
160
+ json: boolean;
161
+ dev: boolean;
162
+ }) => {
163
+ try {
164
+ const credentials = await getStoredCredentials();
165
+ const baseUrl = getBaseUrl(credentials.zone, options.dev);
166
+
167
+ // Filtros iniciales
168
+ const activeFilters: Set<string> = new Set(
169
+ options.filter ? options.filter.split(',').map(f => f.trim()) : []
170
+ );
171
+ let jsonMode = options.json;
172
+ let lastSessionId = '';
173
+
174
+ // Cargar info del agente
175
+ let agentName = 'Agente';
176
+ try {
177
+ process.stdout.write(chalk.gray(' Conectando con agente...'));
178
+ const agent = new Agent({
179
+ workspaceId: credentials.workspace,
180
+ apiKey: credentials.apiKey,
181
+ zone: credentials.zone,
182
+ ...(options.dev && { customUrl: 'http://localhost:5090' }),
183
+ });
184
+ const info: any = await agent.getAgentById({ id: options.agentId });
185
+ if (info?.name) agentName = info.name;
186
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
187
+ } catch {
188
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
189
+ }
190
+
191
+ // Dibujar header
192
+ if (!jsonMode) {
193
+ console.clear();
194
+ printHeader(agentName);
195
+ }
196
+
197
+ // Conectar a SignalR
198
+ const hubUrl = `${baseUrl}/agentMonitorHub`;
199
+ const connection = new HubConnectionBuilder()
200
+ .withUrl(hubUrl, {
201
+ skipNegotiation: true,
202
+ transport: HttpTransportType.WebSockets,
203
+ })
204
+ .withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
205
+ .build();
206
+
207
+ // Evento de log
208
+ connection.on('agent_log', (log: AgentLogEvent) => {
209
+ // Aplicar filtros
210
+ if (activeFilters.size > 0 && !activeFilters.has(log.event_type)) return;
211
+
212
+ const line = renderLogLine(log, {
213
+ showSession: options.session,
214
+ jsonMode,
215
+ }, lastSessionId);
216
+
217
+ console.log(line);
218
+ lastSessionId = log.session_id || lastSessionId;
219
+ });
220
+
221
+ // Eventos de conexion
222
+ connection.onreconnecting(() => {
223
+ if (!jsonMode) {
224
+ console.log(chalk.hex('#FFA726')(' ⟳ Reconectando...'));
225
+ }
226
+ });
227
+
228
+ connection.onreconnected(() => {
229
+ if (!jsonMode) {
230
+ console.log(chalk.hex('#4ade80')(' ● Reconectado'));
231
+ }
232
+ connection.invoke('JoinAgentMonitor', options.agentId).catch(() => {});
233
+ });
234
+
235
+ connection.onclose(() => {
236
+ if (!jsonMode) {
237
+ console.log(chalk.hex('#ef4444')(' ● Desconectado'));
238
+ }
239
+ });
240
+
241
+ // Iniciar conexion
242
+ try {
243
+ await connection.start();
244
+ await connection.invoke('JoinAgentMonitor', options.agentId);
245
+ if (!jsonMode) {
246
+ console.log(chalk.hex('#4ade80')(' ● Conectado al monitor en tiempo real'));
247
+ console.log();
248
+ }
249
+ } catch (err) {
250
+ logger.error(`No se pudo conectar al monitor: ${err instanceof Error ? err.message : err}`);
251
+ process.exit(1);
252
+ }
253
+
254
+ // readline para comandos interactivos
255
+ const rl = readline.createInterface({
256
+ input: process.stdin,
257
+ output: process.stdout,
258
+ prompt: '',
259
+ });
260
+
261
+ // No mostrar prompt para no interferir con el stream
262
+ rl.on('line', (input: string) => {
263
+ const cmd = input.trim().toLowerCase();
264
+
265
+ if (cmd === '/help') {
266
+ console.log(COMMANDS_HELP);
267
+ return;
268
+ }
269
+
270
+ if (cmd === '/clear') {
271
+ console.clear();
272
+ printHeader(agentName);
273
+ return;
274
+ }
275
+
276
+ if (cmd === '/json') {
277
+ jsonMode = !jsonMode;
278
+ console.log(chalk.gray(` Modo JSON: ${jsonMode ? 'activado' : 'desactivado'}`));
279
+ return;
280
+ }
281
+
282
+ if (cmd === '/filters') {
283
+ if (activeFilters.size === 0) {
284
+ console.log(chalk.gray(' Sin filtros activos (mostrando todos)'));
285
+ } else {
286
+ console.log(chalk.gray(' Filtros activos: ') + chalk.white([...activeFilters].join(', ')));
287
+ }
288
+ return;
289
+ }
290
+
291
+ if (cmd.startsWith('/filter ')) {
292
+ const filterType = cmd.substring(8).trim();
293
+ if (!filterType) {
294
+ console.log(chalk.gray(' Uso: /filter <tipo>'));
295
+ return;
296
+ }
297
+ if (activeFilters.has(filterType)) {
298
+ activeFilters.delete(filterType);
299
+ console.log(chalk.gray(` Filtro removido: ${filterType}`));
300
+ } else {
301
+ activeFilters.add(filterType);
302
+ const config = EVENT_COLORS[filterType];
303
+ if (config) {
304
+ console.log(chalk.hex(config.color)(` ● Filtro agregado: ${filterType}`));
305
+ } else {
306
+ console.log(chalk.gray(` Filtro agregado: ${filterType} (tipo desconocido)`));
307
+ }
308
+ }
309
+ if (activeFilters.size > 0) {
310
+ console.log(chalk.gray(' Activos: ') + chalk.white([...activeFilters].join(', ')));
311
+ } else {
312
+ console.log(chalk.gray(' Sin filtros (mostrando todos)'));
313
+ }
314
+ return;
315
+ }
316
+ });
317
+
318
+ // Cierre limpio
319
+ const cleanup = async () => {
320
+ if (!jsonMode) {
321
+ console.log(chalk.gray('\n Cerrando monitor...'));
322
+ }
323
+ try {
324
+ if (connection.state === HubConnectionState.Connected) {
325
+ await connection.invoke('LeaveAgentMonitor', options.agentId);
326
+ }
327
+ await connection.stop();
328
+ } catch {
329
+ // Ignorar errores al cerrar
330
+ }
331
+ rl.close();
332
+ if (!jsonMode) {
333
+ console.log(chalk.gray(' Monitor cerrado.\n'));
334
+ }
335
+ process.exit(0);
336
+ };
337
+
338
+ process.on('SIGINT', cleanup);
339
+ process.on('SIGTERM', cleanup);
340
+
341
+ } catch (error) {
342
+ const message = error instanceof Error ? error.message : 'Error desconocido';
343
+ logger.error(message);
344
+ process.exit(1);
345
+ }
346
+ });