plazbot-cli 0.2.3 → 0.2.5
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/dist/commands/agent/create.js +4 -2
- package/dist/commands/agent/get.js +128 -93
- package/dist/commands/agent/index.js +3 -1
- package/dist/commands/agent/monitor.js +299 -0
- package/dist/commands/agent/wizard.js +173 -9
- package/package.json +2 -1
- package/src/commands/agent/create.ts +4 -2
- package/src/commands/agent/get.ts +140 -101
- package/src/commands/agent/index.ts +3 -1
- package/src/commands/agent/monitor.ts +352 -0
- package/src/commands/agent/wizard.ts +200 -9
|
@@ -0,0 +1,352 @@
|
|
|
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')('/filter clear')} Quitar todos los filtros
|
|
139
|
+
${chalk.hex('#4CAF50')('/filters')} Mostrar filtros activos
|
|
140
|
+
${chalk.hex('#4CAF50')('/clear')} Limpiar pantalla
|
|
141
|
+
${chalk.hex('#4CAF50')('/json')} Toggle modo JSON expandido
|
|
142
|
+
${chalk.hex('#4CAF50')('/help')} Mostrar estos comandos
|
|
143
|
+
|
|
144
|
+
${chalk.bold('Tipos de evento:')}
|
|
145
|
+
${Object.entries(EVENT_COLORS).map(([key, val]) =>
|
|
146
|
+
` ${chalk.hex(val.color)('●')} ${key}`
|
|
147
|
+
).join('\n')}
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
export const monitorCommand = new Command('monitor')
|
|
151
|
+
.description('Monitor en tiempo real de un agente (logs via SignalR)')
|
|
152
|
+
.requiredOption('-a, --agent-id <id>', 'ID del agente')
|
|
153
|
+
.option('-f, --filter <types>', 'Filtrar por tipos separados por coma (ej: msg_in,msg_out)')
|
|
154
|
+
.option('--no-session', 'Ocultar session ID')
|
|
155
|
+
.option('--json', 'Modo JSON crudo (para piping)', false)
|
|
156
|
+
.option('--dev', 'Usar ambiente de desarrollo', false)
|
|
157
|
+
.action(async (options: {
|
|
158
|
+
agentId: string;
|
|
159
|
+
filter?: string;
|
|
160
|
+
session: boolean;
|
|
161
|
+
json: boolean;
|
|
162
|
+
dev: boolean;
|
|
163
|
+
}) => {
|
|
164
|
+
try {
|
|
165
|
+
const credentials = await getStoredCredentials();
|
|
166
|
+
const baseUrl = getBaseUrl(credentials.zone, options.dev);
|
|
167
|
+
|
|
168
|
+
// Filtros iniciales
|
|
169
|
+
const activeFilters: Set<string> = new Set(
|
|
170
|
+
options.filter ? options.filter.split(',').map(f => f.trim()) : []
|
|
171
|
+
);
|
|
172
|
+
let jsonMode = options.json;
|
|
173
|
+
let lastSessionId = '';
|
|
174
|
+
|
|
175
|
+
// Cargar info del agente
|
|
176
|
+
let agentName = 'Agente';
|
|
177
|
+
try {
|
|
178
|
+
process.stdout.write(chalk.gray(' Conectando con agente...'));
|
|
179
|
+
const agent = new Agent({
|
|
180
|
+
workspaceId: credentials.workspace,
|
|
181
|
+
apiKey: credentials.apiKey,
|
|
182
|
+
zone: credentials.zone,
|
|
183
|
+
...(options.dev && { customUrl: 'http://localhost:5090' }),
|
|
184
|
+
});
|
|
185
|
+
const info: any = await agent.getAgentById({ id: options.agentId });
|
|
186
|
+
if (info?.name) agentName = info.name;
|
|
187
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
188
|
+
} catch {
|
|
189
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Dibujar header
|
|
193
|
+
if (!jsonMode) {
|
|
194
|
+
console.clear();
|
|
195
|
+
printHeader(agentName);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Conectar a SignalR
|
|
199
|
+
const hubUrl = `${baseUrl}/agentMonitorHub`;
|
|
200
|
+
const connection = new HubConnectionBuilder()
|
|
201
|
+
.withUrl(hubUrl, {
|
|
202
|
+
skipNegotiation: true,
|
|
203
|
+
transport: HttpTransportType.WebSockets,
|
|
204
|
+
})
|
|
205
|
+
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
|
|
206
|
+
.build();
|
|
207
|
+
|
|
208
|
+
// Evento de log
|
|
209
|
+
connection.on('agent_log', (log: AgentLogEvent) => {
|
|
210
|
+
// Aplicar filtros
|
|
211
|
+
if (activeFilters.size > 0 && !activeFilters.has(log.event_type)) return;
|
|
212
|
+
|
|
213
|
+
const line = renderLogLine(log, {
|
|
214
|
+
showSession: options.session,
|
|
215
|
+
jsonMode,
|
|
216
|
+
}, lastSessionId);
|
|
217
|
+
|
|
218
|
+
console.log(line);
|
|
219
|
+
lastSessionId = log.session_id || lastSessionId;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Eventos de conexion
|
|
223
|
+
connection.onreconnecting(() => {
|
|
224
|
+
if (!jsonMode) {
|
|
225
|
+
console.log(chalk.hex('#FFA726')(' ⟳ Reconectando...'));
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
connection.onreconnected(() => {
|
|
230
|
+
if (!jsonMode) {
|
|
231
|
+
console.log(chalk.hex('#4ade80')(' ● Reconectado'));
|
|
232
|
+
}
|
|
233
|
+
connection.invoke('JoinAgentMonitor', options.agentId).catch(() => {});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
connection.onclose(() => {
|
|
237
|
+
if (!jsonMode) {
|
|
238
|
+
console.log(chalk.hex('#ef4444')(' ● Desconectado'));
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Iniciar conexion
|
|
243
|
+
try {
|
|
244
|
+
await connection.start();
|
|
245
|
+
await connection.invoke('JoinAgentMonitor', options.agentId);
|
|
246
|
+
if (!jsonMode) {
|
|
247
|
+
console.log(chalk.hex('#4ade80')(' ● Conectado al monitor en tiempo real'));
|
|
248
|
+
console.log();
|
|
249
|
+
}
|
|
250
|
+
} catch (err) {
|
|
251
|
+
logger.error(`No se pudo conectar al monitor: ${err instanceof Error ? err.message : err}`);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// readline para comandos interactivos
|
|
256
|
+
const rl = readline.createInterface({
|
|
257
|
+
input: process.stdin,
|
|
258
|
+
output: process.stdout,
|
|
259
|
+
prompt: '',
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// No mostrar prompt para no interferir con el stream
|
|
263
|
+
rl.on('line', (input: string) => {
|
|
264
|
+
const cmd = input.trim().toLowerCase();
|
|
265
|
+
|
|
266
|
+
if (cmd === '/help') {
|
|
267
|
+
console.log(COMMANDS_HELP);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (cmd === '/clear') {
|
|
272
|
+
console.clear();
|
|
273
|
+
printHeader(agentName);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (cmd === '/json') {
|
|
278
|
+
jsonMode = !jsonMode;
|
|
279
|
+
console.log(chalk.gray(` Modo JSON: ${jsonMode ? 'activado' : 'desactivado'}`));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (cmd === '/filters') {
|
|
284
|
+
if (activeFilters.size === 0) {
|
|
285
|
+
console.log(chalk.gray(' Sin filtros activos (mostrando todos)'));
|
|
286
|
+
} else {
|
|
287
|
+
console.log(chalk.gray(' Filtros activos: ') + chalk.white([...activeFilters].join(', ')));
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (cmd.startsWith('/filter ')) {
|
|
293
|
+
const filterType = cmd.substring(8).trim();
|
|
294
|
+
if (!filterType) {
|
|
295
|
+
console.log(chalk.gray(' Uso: /filter <tipo> | /filter clear'));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (filterType === 'clear') {
|
|
299
|
+
activeFilters.clear();
|
|
300
|
+
console.log(chalk.gray(' Todos los filtros removidos (mostrando todos)'));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (activeFilters.has(filterType)) {
|
|
304
|
+
activeFilters.delete(filterType);
|
|
305
|
+
console.log(chalk.gray(` Filtro removido: ${filterType}`));
|
|
306
|
+
} else {
|
|
307
|
+
activeFilters.add(filterType);
|
|
308
|
+
const config = EVENT_COLORS[filterType];
|
|
309
|
+
if (config) {
|
|
310
|
+
console.log(chalk.hex(config.color)(` ● Filtro agregado: ${filterType}`));
|
|
311
|
+
} else {
|
|
312
|
+
console.log(chalk.gray(` Filtro agregado: ${filterType} (tipo desconocido)`));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (activeFilters.size > 0) {
|
|
316
|
+
console.log(chalk.gray(' Activos: ') + chalk.white([...activeFilters].join(', ')));
|
|
317
|
+
} else {
|
|
318
|
+
console.log(chalk.gray(' Sin filtros (mostrando todos)'));
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Cierre limpio
|
|
325
|
+
const cleanup = async () => {
|
|
326
|
+
if (!jsonMode) {
|
|
327
|
+
console.log(chalk.gray('\n Cerrando monitor...'));
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
if (connection.state === HubConnectionState.Connected) {
|
|
331
|
+
await connection.invoke('LeaveAgentMonitor', options.agentId);
|
|
332
|
+
}
|
|
333
|
+
await connection.stop();
|
|
334
|
+
} catch {
|
|
335
|
+
// Ignorar errores al cerrar
|
|
336
|
+
}
|
|
337
|
+
rl.close();
|
|
338
|
+
if (!jsonMode) {
|
|
339
|
+
console.log(chalk.gray(' Monitor cerrado.\n'));
|
|
340
|
+
}
|
|
341
|
+
process.exit(0);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
process.on('SIGINT', cleanup);
|
|
345
|
+
process.on('SIGTERM', cleanup);
|
|
346
|
+
|
|
347
|
+
} catch (error) {
|
|
348
|
+
const message = error instanceof Error ? error.message : 'Error desconocido';
|
|
349
|
+
logger.error(message);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import ora from 'ora';
|
|
3
5
|
import { theme, section, kvPair } from '../../utils/ui';
|
|
4
6
|
|
|
5
7
|
interface AgentConfig {
|
|
@@ -94,7 +96,187 @@ const MODELS: Record<string, string[]> = {
|
|
|
94
96
|
|
|
95
97
|
const COLORS = ['blue', 'green', 'orange', 'gray', 'white'];
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
interface WizardContext {
|
|
100
|
+
zone: string;
|
|
101
|
+
workspaceId: string;
|
|
102
|
+
apiKey: string;
|
|
103
|
+
dev: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getBaseUrl(zone: string, dev: boolean): string {
|
|
107
|
+
if (dev) return 'http://localhost:5090';
|
|
108
|
+
return zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const CHANNEL_CHOICES = [
|
|
112
|
+
{ name: 'WhatsApp', value: 'whatsapp' },
|
|
113
|
+
{ name: 'WhatsApp Business', value: 'whatsapp_business' },
|
|
114
|
+
{ name: 'Instagram', value: 'instagram' },
|
|
115
|
+
{ name: 'Messenger', value: 'facebook' },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const CHANNEL_AGENT_MAP: Record<string, string> = {
|
|
119
|
+
whatsapp: 'whatsapp',
|
|
120
|
+
whatsapp_business: 'whatsapp',
|
|
121
|
+
instagram: 'instagram',
|
|
122
|
+
facebook: 'facebook',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
async function connectChannelFlow(ctx: WizardContext): Promise<AgentChannel[]> {
|
|
126
|
+
const channels: AgentChannel[] = [];
|
|
127
|
+
const baseUrl = getBaseUrl(ctx.zone, ctx.dev);
|
|
128
|
+
const headers = {
|
|
129
|
+
'Authorization': `Bearer ${ctx.apiKey}`,
|
|
130
|
+
'x-workspace-id': ctx.workspaceId,
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
let addMore = true;
|
|
135
|
+
while (addMore) {
|
|
136
|
+
const { channelType } = await (inquirer as any).prompt([{
|
|
137
|
+
type: 'list',
|
|
138
|
+
name: 'channelType',
|
|
139
|
+
message: 'Que canal deseas conectar?',
|
|
140
|
+
choices: CHANNEL_CHOICES,
|
|
141
|
+
}]);
|
|
142
|
+
|
|
143
|
+
const channelLabel = CHANNEL_CHOICES.find(c => c.value === channelType)?.name || channelType;
|
|
144
|
+
|
|
145
|
+
// Generar link de onboarding
|
|
146
|
+
const spinner = ora({ text: chalk.gray('Generando link de conexion...'), spinner: 'dots', color: 'green' }).start();
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const linkRes = await axios.post(
|
|
150
|
+
`${baseUrl}/api/workspace/${ctx.workspaceId}/onboarding-link`,
|
|
151
|
+
{ type: channelType },
|
|
152
|
+
{ headers }
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!linkRes.data?.success) {
|
|
156
|
+
spinner.fail(chalk.hex('#EF5350')(`Error: ${linkRes.data?.message || 'No se pudo generar el link'}`));
|
|
157
|
+
const { tryManual } = await (inquirer as any).prompt([{
|
|
158
|
+
type: 'confirm', name: 'tryManual',
|
|
159
|
+
message: 'Deseas ingresar el numero manualmente?', default: false,
|
|
160
|
+
}]);
|
|
161
|
+
if (tryManual) {
|
|
162
|
+
const { key } = await (inquirer as any).prompt([
|
|
163
|
+
{ type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v: string) => v.length > 0 || 'Requerido' },
|
|
164
|
+
]);
|
|
165
|
+
channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
|
|
166
|
+
}
|
|
167
|
+
const { more } = await (inquirer as any).prompt([{ type: 'confirm', name: 'more', message: 'Conectar otro canal?', default: false }]);
|
|
168
|
+
addMore = more;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const shortUrl = linkRes.data.data?.shortUrl || '';
|
|
173
|
+
spinner.stop();
|
|
174
|
+
|
|
175
|
+
// Mostrar link en box visual
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(chalk.hex('#4CAF50')(' ┌' + '─'.repeat(64) + '┐'));
|
|
178
|
+
console.log(chalk.hex('#4CAF50')(' │') + chalk.bold(` Envia este link para conectar ${channelLabel}:`).padEnd(74) + chalk.hex('#4CAF50')('│'));
|
|
179
|
+
console.log(chalk.hex('#4CAF50')(' │') + ' '.padEnd(64) + chalk.hex('#4CAF50')('│'));
|
|
180
|
+
console.log(chalk.hex('#4CAF50')(' │') + chalk.hex('#22d3ee')(` ${shortUrl}`).padEnd(74) + chalk.hex('#4CAF50')('│'));
|
|
181
|
+
console.log(chalk.hex('#4CAF50')(' │') + ' '.padEnd(64) + chalk.hex('#4CAF50')('│'));
|
|
182
|
+
console.log(chalk.hex('#4CAF50')(' └' + '─'.repeat(64) + '┘'));
|
|
183
|
+
console.log();
|
|
184
|
+
|
|
185
|
+
// Snapshot de integraciones actuales
|
|
186
|
+
let prevIntegrationIds: Set<string> = new Set();
|
|
187
|
+
try {
|
|
188
|
+
const wkRes = await axios.get(`${baseUrl}/api/workspace/${ctx.workspaceId}`, { headers });
|
|
189
|
+
const integrations = wkRes.data?.integrations || wkRes.data?.data?.integrations || [];
|
|
190
|
+
prevIntegrationIds = new Set(integrations.map((i: any) => i.id));
|
|
191
|
+
} catch {
|
|
192
|
+
// Si falla, se parte de vacio
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Polling para detectar nueva integracion
|
|
196
|
+
const pollSpinner = ora({ text: chalk.gray('Esperando conexion... (Ctrl+C para cancelar)'), spinner: 'dots', color: 'cyan' }).start();
|
|
197
|
+
const POLL_INTERVAL = 5000;
|
|
198
|
+
const MAX_POLLS = 60; // 5 minutos
|
|
199
|
+
let detected: any = null;
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < MAX_POLLS; i++) {
|
|
202
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL));
|
|
203
|
+
try {
|
|
204
|
+
const wkRes = await axios.get(`${baseUrl}/api/workspace/${ctx.workspaceId}`, { headers });
|
|
205
|
+
const integrations = wkRes.data?.integrations || wkRes.data?.data?.integrations || [];
|
|
206
|
+
|
|
207
|
+
// Buscar integraciones nuevas del tipo correcto
|
|
208
|
+
const targetType = channelType === 'whatsapp_business' ? 'whatsapp' : channelType;
|
|
209
|
+
const newOnes = integrations.filter((ig: any) =>
|
|
210
|
+
!prevIntegrationIds.has(ig.id) && ig.type === targetType
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (newOnes.length > 0) {
|
|
214
|
+
detected = newOnes[0];
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// Ignorar errores de poll individual
|
|
219
|
+
}
|
|
220
|
+
const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
|
|
221
|
+
pollSpinner.text = chalk.gray(`Esperando conexion... ${elapsed}s`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (detected) {
|
|
225
|
+
pollSpinner.succeed(chalk.hex('#4ade80')(`Conexion detectada: ${detected.aliasName || detected.id} (${channelLabel})`));
|
|
226
|
+
|
|
227
|
+
const { associate } = await (inquirer as any).prompt([{
|
|
228
|
+
type: 'confirm', name: 'associate',
|
|
229
|
+
message: 'Deseas asociar este canal al agente?', default: true,
|
|
230
|
+
}]);
|
|
231
|
+
|
|
232
|
+
if (associate) {
|
|
233
|
+
channels.push({
|
|
234
|
+
channel: CHANNEL_AGENT_MAP[channelType],
|
|
235
|
+
key: detected.id,
|
|
236
|
+
multianswer: false,
|
|
237
|
+
});
|
|
238
|
+
console.log(chalk.hex('#4ade80')(' Canal asociado correctamente.'));
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
pollSpinner.warn(chalk.hex('#FFA726')('Timeout: no se detecto una conexion.'));
|
|
242
|
+
const { action } = await (inquirer as any).prompt([{
|
|
243
|
+
type: 'list', name: 'action',
|
|
244
|
+
message: 'Que deseas hacer?',
|
|
245
|
+
choices: [
|
|
246
|
+
{ name: 'Ingresar numero/ID manualmente', value: 'manual' },
|
|
247
|
+
{ name: 'Saltar este paso', value: 'skip' },
|
|
248
|
+
],
|
|
249
|
+
}]);
|
|
250
|
+
if (action === 'manual') {
|
|
251
|
+
const { key } = await (inquirer as any).prompt([
|
|
252
|
+
{ type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v: string) => v.length > 0 || 'Requerido' },
|
|
253
|
+
]);
|
|
254
|
+
channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
} catch (err) {
|
|
259
|
+
spinner.fail(chalk.hex('#EF5350')(`Error al generar link: ${err instanceof Error ? err.message : err}`));
|
|
260
|
+
const { tryManual } = await (inquirer as any).prompt([{
|
|
261
|
+
type: 'confirm', name: 'tryManual',
|
|
262
|
+
message: 'Deseas ingresar el numero manualmente?', default: false,
|
|
263
|
+
}]);
|
|
264
|
+
if (tryManual) {
|
|
265
|
+
const { key } = await (inquirer as any).prompt([
|
|
266
|
+
{ type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v: string) => v.length > 0 || 'Requerido' },
|
|
267
|
+
]);
|
|
268
|
+
channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const { more } = await (inquirer as any).prompt([{ type: 'confirm', name: 'more', message: 'Conectar otro canal?', default: false }]);
|
|
273
|
+
addMore = more;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return channels;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export async function runAgentWizard(zone: string, workspaceId?: string, apiKey?: string, dev?: boolean): Promise<AgentConfig> {
|
|
98
280
|
console.log(section('Crear nuevo agente de IA'));
|
|
99
281
|
console.log(theme.muted(' Responde las siguientes preguntas para configurar tu agente.\n'));
|
|
100
282
|
|
|
@@ -383,17 +565,20 @@ export async function runAgentWizard(zone: string): Promise<AgentConfig> {
|
|
|
383
565
|
});
|
|
384
566
|
}
|
|
385
567
|
|
|
386
|
-
// Paso 7:
|
|
387
|
-
console.log(theme.bold('\n Paso 7/8:
|
|
388
|
-
|
|
389
|
-
const {
|
|
568
|
+
// Paso 7: Conectar canal
|
|
569
|
+
console.log(theme.bold('\n Paso 7/8: Conectar canal'));
|
|
570
|
+
let channels: AgentChannel[] = [];
|
|
571
|
+
const { addChannel } = await (inquirer as any).prompt([{
|
|
390
572
|
type: 'confirm',
|
|
391
|
-
name: '
|
|
392
|
-
message: '
|
|
573
|
+
name: 'addChannel',
|
|
574
|
+
message: 'Deseas conectar un canal (WhatsApp, Instagram, Messenger)?',
|
|
393
575
|
default: false,
|
|
394
576
|
}]);
|
|
395
577
|
|
|
396
|
-
if (
|
|
578
|
+
if (addChannel && workspaceId && apiKey) {
|
|
579
|
+
channels = await connectChannelFlow({ zone, workspaceId, apiKey, dev: dev || false });
|
|
580
|
+
} else if (addChannel) {
|
|
581
|
+
// Fallback manual si no hay credenciales para API
|
|
397
582
|
const wa = await (inquirer as any).prompt([
|
|
398
583
|
{ type: 'input', name: 'key', message: 'Numero de WhatsApp (con codigo de pais):', validate: (v: string) => v.length > 0 || 'Requerido' },
|
|
399
584
|
{ type: 'confirm', name: 'multianswer', message: 'Permitir multiples respuestas?', default: false },
|
|
@@ -468,7 +653,13 @@ export async function runAgentWizard(zone: string): Promise<AgentConfig> {
|
|
|
468
653
|
console.log(kvPair('Servicios', String(config.services.length)));
|
|
469
654
|
console.log(kvPair('Acciones', String(config.actions.length)));
|
|
470
655
|
console.log(kvPair('AI Provider', config.customAIConfig ? config.aiProviders[0]?.provider + ' / ' + config.aiProviders[0]?.model : 'Default (Plazbot)'));
|
|
471
|
-
|
|
656
|
+
if (config.channels.length > 0) {
|
|
657
|
+
config.channels.forEach((ch, i) => {
|
|
658
|
+
console.log(kvPair(`Canal ${i + 1} (${ch.channel})`, ch.key));
|
|
659
|
+
});
|
|
660
|
+
} else {
|
|
661
|
+
console.log(kvPair('Canales', 'No configurado'));
|
|
662
|
+
}
|
|
472
663
|
console.log();
|
|
473
664
|
|
|
474
665
|
return config;
|