plazbot-cli 0.2.9 → 0.2.11

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.
@@ -30,7 +30,8 @@ exports.aiConfigCommand = new commander_1.Command('ai-config')
30
30
  });
31
31
  const spinner = (0, ui_1.createSpinner)('Cargando agente...');
32
32
  spinner.start();
33
- const agentData = await agent.getAgentById({ id: agentId });
33
+ const _res = await agent.getAgentById({ id: agentId });
34
+ const agentData = _res.agent || _res.data || _res;
34
35
  spinner.stop();
35
36
  console.log((0, ui_1.section)('Configuracion de IA - ' + (agentData.name || agentId)));
36
37
  const currentProviders = agentData.aiProviders || [];
@@ -41,7 +41,8 @@ exports.chatCommand = new commander_1.Command('chat')
41
41
  let agentInfo = null;
42
42
  try {
43
43
  process.stdout.write(chalk_1.default.gray(' Conectando con agente...'));
44
- agentInfo = await agent.getAgentById({ id: options.agentId });
44
+ const _res = await agent.getAgentById({ id: options.agentId });
45
+ agentInfo = _res.agent || _res.data || _res;
45
46
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
46
47
  }
47
48
  catch {
@@ -24,12 +24,13 @@ exports.deleteCommand = new commander_1.Command('delete')
24
24
  ...(options.dev && { customUrl: "http://localhost:5090" })
25
25
  });
26
26
  // Obtener detalles del agente para mostrar información
27
- const agentDetails = await agent.getAgentById({ id: agentId });
27
+ const response = await agent.getAgentById({ id: agentId });
28
+ const agentDetails = response.agent || response.data || response;
28
29
  logger_1.logger.warning('\nVas a eliminar el siguiente agente:');
29
30
  logger_1.logger.divider();
30
- logger_1.logger.info(`ID: ${agentDetails.id}`);
31
- logger_1.logger.info(`Nombre: ${agentDetails.name}`);
32
- logger_1.logger.info(`Descripción: ${agentDetails.description}`);
31
+ logger_1.logger.info(`ID: ${agentDetails.id || agentId}`);
32
+ logger_1.logger.info(`Nombre: ${agentDetails.name || '—'}`);
33
+ logger_1.logger.info(`Descripcion: ${agentDetails.description || '—'}`);
33
34
  logger_1.logger.divider();
34
35
  // Crear interfaz para confirmación
35
36
  const rl = readline_1.default.createInterface({
@@ -21,7 +21,8 @@ exports.enableCommand = new commander_1.Command('enable-widget')
21
21
  ...(options.dev && { customUrl: "http://localhost:5090" })
22
22
  });
23
23
  // Obtener estado actual del agente
24
- const agentDetails = await agent.getAgentById({ id: agentId });
24
+ const _res = await agent.getAgentById({ id: agentId });
25
+ const agentDetails = _res.agent || _res.data || _res;
25
26
  logger_1.logger.title('Estado actual del widget');
26
27
  logger_1.logger.label('Agente', agentDetails.name);
27
28
  // Cambiar estado
@@ -24,7 +24,8 @@ filesGroup.command('list')
24
24
  });
25
25
  const spinner = (0, ui_1.createSpinner)('Cargando archivos...');
26
26
  spinner.start();
27
- const agentData = await agent.getAgentById({ id: agentId });
27
+ const _res = await agent.getAgentById({ id: agentId });
28
+ const agentData = _res.agent || _res.data || _res;
28
29
  spinner.stop();
29
30
  const files = agentData.files || [];
30
31
  console.log((0, ui_1.section)('Archivos - ' + (agentData.name || agentId)));
@@ -145,7 +145,8 @@ exports.monitorCommand = new commander_1.Command('monitor')
145
145
  zone: effectiveZone,
146
146
  ...(options.dev && { customUrl: 'http://localhost:5090' }),
147
147
  });
148
- const info = await agent.getAgentById({ id: options.agentId });
148
+ const _res = await agent.getAgentById({ id: options.agentId });
149
+ const info = _res.agent || _res.data || _res;
149
150
  if (info?.name)
150
151
  agentName = info.name;
151
152
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
@@ -56,7 +56,8 @@ setGroup.command('instructions')
56
56
  // Cargar instrucciones actuales
57
57
  const loadSpinner = (0, ui_1.createSpinner)('Cargando...');
58
58
  loadSpinner.start();
59
- const agentData = await agent.getAgentById({ id: agentId });
59
+ const _res1 = await agent.getAgentById({ id: agentId });
60
+ const agentData = _res1.agent || _res1.data || _res1;
60
61
  loadSpinner.stop();
61
62
  const current = agentData.instructions || {};
62
63
  console.log((0, ui_1.section)('Instrucciones - ' + (agentData.name || agentId)));
@@ -104,7 +105,8 @@ setGroup.command('persona')
104
105
  });
105
106
  const loadSpinner = (0, ui_1.createSpinner)('Cargando...');
106
107
  loadSpinner.start();
107
- const agentData = await agent.getAgentById({ id: agentId });
108
+ const _res2 = await agent.getAgentById({ id: agentId });
109
+ const agentData = _res2.agent || _res2.data || _res2;
108
110
  loadSpinner.stop();
109
111
  const current = agentData.person || {};
110
112
  console.log((0, ui_1.section)('Persona - ' + (agentData.name || agentId)));
@@ -25,7 +25,8 @@ exports.toolsCommand = new commander_1.Command('tools')
25
25
  });
26
26
  const spinner = (0, ui_1.createSpinner)('Cargando agente...');
27
27
  spinner.start();
28
- const agentData = await agent.getAgentById({ id: agentId });
28
+ const _res = await agent.getAgentById({ id: agentId });
29
+ const agentData = _res.agent || _res.data || _res;
29
30
  spinner.stop();
30
31
  console.log((0, ui_1.section)('Tool Calling - ' + (agentData.name || agentId)));
31
32
  console.log((0, ui_1.kvPair)('Estado', agentData.useToolCalling ? ui_1.theme.success('Activado') : ui_1.theme.error('Desactivado')));
@@ -32,6 +32,24 @@ const CHANNEL_AGENT_MAP = {
32
32
  instagram: 'instagram',
33
33
  facebook: 'facebook',
34
34
  };
35
+ function getIntegrationLabel(ig) {
36
+ if (ig.type === 'whatsapp') {
37
+ return ig.aliasName
38
+ ? `${ig.aliasName} (${ig.cellphoneNumberFormat || ig.cellphoneNumber || ''})`
39
+ : ig.cellphoneNumberFormat || ig.cellphoneNumber || ig.id;
40
+ }
41
+ return ig.aliasName || ig.srcName || ig.id;
42
+ }
43
+ async function fetchWorkspaceIntegrations(baseUrl, workspaceId, headers) {
44
+ try {
45
+ const wkRes = await axios_1.default.get(`${baseUrl}/api/workspace/${workspaceId}`, { headers });
46
+ const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
47
+ return wsData?.integrations || [];
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ }
35
53
  async function connectChannelFlow(ctx) {
36
54
  const channels = [];
37
55
  const baseUrl = getBaseUrl(ctx.zone, ctx.dev);
@@ -49,156 +67,145 @@ async function connectChannelFlow(ctx) {
49
67
  choices: CHANNEL_CHOICES,
50
68
  }]);
51
69
  const channelLabel = CHANNEL_CHOICES.find(c => c.value === channelType)?.name || channelType;
52
- // Generar link de onboarding
53
- const spinner = (0, ora_1.default)({ text: chalk_1.default.gray('Generando link de conexion...'), spinner: 'dots', color: 'green' }).start();
54
- try {
55
- const linkRes = await axios_1.default.post(`${baseUrl}/api/workspace/${ctx.workspaceId}/onboarding-link`, { type: channelType }, { headers });
56
- if (!linkRes.data?.success) {
57
- spinner.fail(chalk_1.default.hex('#EF5350')(`Error: ${linkRes.data?.message || 'No se pudo generar el link'}`));
58
- const { tryManual } = await inquirer_1.default.prompt([{
59
- type: 'confirm', name: 'tryManual',
60
- message: 'Deseas ingresar el numero manualmente?', default: false,
61
- }]);
62
- if (tryManual) {
63
- const { key } = await inquirer_1.default.prompt([
64
- { type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v) => v.length > 0 || 'Requerido' },
65
- ]);
66
- channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
67
- }
68
- const { more } = await inquirer_1.default.prompt([{ type: 'confirm', name: 'more', message: 'Conectar otro canal?', default: false }]);
69
- addMore = more;
70
- continue;
70
+ const targetType = channelType === 'whatsapp_business' ? 'whatsapp' : channelType;
71
+ // Cargar integraciones existentes del tipo seleccionado
72
+ const loadSpinner = (0, ora_1.default)({ text: chalk_1.default.gray('Cargando integraciones...'), spinner: 'dots', color: 'green' }).start();
73
+ const allIntegrations = await fetchWorkspaceIntegrations(baseUrl, ctx.workspaceId, headers);
74
+ const existing = allIntegrations.filter((ig) => ig.type === targetType);
75
+ loadSpinner.stop();
76
+ let selectedIntegration = null;
77
+ if (existing.length > 0) {
78
+ // Mostrar integraciones existentes + opcion de agregar nueva
79
+ const choices = existing.map((ig) => ({
80
+ name: `${getIntegrationLabel(ig)}`,
81
+ value: ig.id,
82
+ }));
83
+ choices.push(new inquirer_1.default.Separator());
84
+ choices.push({ name: chalk_1.default.hex('#4ade80')('+ Agregar uno nuevo (generar link)'), value: '__new__' });
85
+ const { selected } = await inquirer_1.default.prompt([{
86
+ type: 'list',
87
+ name: 'selected',
88
+ message: `Integraciones de ${channelLabel} disponibles:`,
89
+ choices,
90
+ }]);
91
+ if (selected !== '__new__') {
92
+ // Selecciono una existente
93
+ selectedIntegration = existing.find((ig) => ig.id === selected);
94
+ const label = getIntegrationLabel(selectedIntegration);
95
+ channels.push({
96
+ channel: CHANNEL_AGENT_MAP[channelType],
97
+ key: selectedIntegration.id,
98
+ multianswer: false,
99
+ });
100
+ console.log(chalk_1.default.hex('#4ade80')(` Canal asociado: ${label}`));
71
101
  }
72
- const shortUrl = linkRes.data.data?.shortUrl || '';
73
- spinner.stop();
74
- // Mostrar link
75
- console.log();
76
- console.log(chalk_1.default.hex('#4CAF50')(' ─'.repeat(32)));
77
- console.log(chalk_1.default.bold(` Envia este link para conectar ${channelLabel}:`));
78
- console.log();
79
- console.log(' ' + chalk_1.default.hex('#22d3ee')(shortUrl));
80
- console.log();
81
- console.log(chalk_1.default.hex('#4CAF50')(' ─'.repeat(32)));
82
- console.log();
83
- // Snapshot de integraciones actuales
84
- let prevIntegrationIds = new Set();
102
+ }
103
+ // Si no hay existentes o eligio "Agregar nuevo" → generar link de onboarding
104
+ if (!selectedIntegration) {
105
+ const spinner = (0, ora_1.default)({ text: chalk_1.default.gray('Generando link de conexion...'), spinner: 'dots', color: 'green' }).start();
85
106
  try {
86
- const wkRes = await axios_1.default.get(`${baseUrl}/api/workspace/${ctx.workspaceId}`, { headers });
87
- // La API retorna un array [{ integrations: [...] }]
88
- const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
89
- const integrations = wsData?.integrations || [];
90
- prevIntegrationIds = new Set(integrations.map((i) => i.id));
91
- }
92
- catch {
93
- // Si falla, se parte de vacio
94
- }
95
- // Polling para detectar nueva integracion (Enter para saltar)
96
- const pollSpinner = (0, ora_1.default)({ text: chalk_1.default.gray('Esperando conexion... (Enter para saltar)'), spinner: 'dots', color: 'cyan' }).start();
97
- const POLL_INTERVAL = 5000;
98
- const MAX_POLLS = 60; // 5 minutos
99
- let detected = null;
100
- let skipped = false;
101
- // Escuchar Enter para saltar el polling
102
- const skipPromise = new Promise(resolve => {
103
- const onData = (data) => {
104
- if (data.toString().includes('\n') || data.toString().includes('\r')) {
105
- skipped = true;
106
- process.stdin.removeListener('data', onData);
107
- if (wasRaw)
108
- process.stdin.setRawMode(false);
109
- resolve();
110
- }
111
- };
112
- const wasRaw = process.stdin.isRaw;
113
- if (process.stdin.isTTY)
114
- process.stdin.setRawMode(true);
115
- process.stdin.resume();
116
- process.stdin.on('data', onData);
117
- // Limpiar si termina el polling naturalmente
118
- setTimeout(() => {
119
- process.stdin.removeListener('data', onData);
120
- if (process.stdin.isTTY && wasRaw !== undefined) {
107
+ const linkRes = await axios_1.default.post(`${baseUrl}/api/workspace/${ctx.workspaceId}/onboarding-link`, { type: channelType }, { headers });
108
+ if (!linkRes.data?.success) {
109
+ spinner.fail(chalk_1.default.hex('#EF5350')(`Error: ${linkRes.data?.message || 'No se pudo generar el link'}`));
110
+ }
111
+ else {
112
+ const shortUrl = linkRes.data.data?.shortUrl || '';
113
+ spinner.stop();
114
+ // Mostrar link
115
+ console.log();
116
+ console.log(chalk_1.default.hex('#4CAF50')(' ─'.repeat(32)));
117
+ console.log(chalk_1.default.bold(` Envia este link para conectar ${channelLabel}:`));
118
+ console.log();
119
+ console.log(' ' + chalk_1.default.hex('#22d3ee')(shortUrl));
120
+ console.log();
121
+ console.log(chalk_1.default.hex('#4CAF50')(' ─'.repeat(32)));
122
+ console.log();
123
+ // Guardar IDs actuales para detectar nuevos
124
+ const prevIds = new Set(allIntegrations.map((i) => i.id));
125
+ // Polling para detectar nueva integracion (Enter para saltar)
126
+ const pollSpinner = (0, ora_1.default)({ text: chalk_1.default.gray('Esperando conexion... (Enter para saltar)'), spinner: 'dots', color: 'cyan' }).start();
127
+ const POLL_INTERVAL = 5000;
128
+ const MAX_POLLS = 60;
129
+ let detected = null;
130
+ let skipped = false;
131
+ // Escuchar Enter para saltar
132
+ const skipPromise = new Promise(resolve => {
133
+ const onData = (data) => {
134
+ if (data.toString().includes('\n') || data.toString().includes('\r')) {
135
+ skipped = true;
136
+ process.stdin.removeListener('data', onData);
137
+ if (wasRaw)
138
+ process.stdin.setRawMode(false);
139
+ resolve();
140
+ }
141
+ };
142
+ const wasRaw = process.stdin.isRaw;
143
+ if (process.stdin.isTTY)
144
+ process.stdin.setRawMode(true);
145
+ process.stdin.resume();
146
+ process.stdin.on('data', onData);
147
+ setTimeout(() => {
148
+ process.stdin.removeListener('data', onData);
149
+ if (process.stdin.isTTY && wasRaw !== undefined) {
150
+ try {
151
+ process.stdin.setRawMode(wasRaw);
152
+ }
153
+ catch { }
154
+ }
155
+ resolve();
156
+ }, POLL_INTERVAL * MAX_POLLS + 1000);
157
+ });
158
+ for (let i = 0; i < MAX_POLLS; i++) {
159
+ await Promise.race([
160
+ new Promise(r => setTimeout(r, POLL_INTERVAL)),
161
+ skipPromise,
162
+ ]);
163
+ if (skipped)
164
+ break;
121
165
  try {
122
- process.stdin.setRawMode(wasRaw);
166
+ const freshIntegrations = await fetchWorkspaceIntegrations(baseUrl, ctx.workspaceId, headers);
167
+ const newOnes = freshIntegrations.filter((ig) => !prevIds.has(ig.id));
168
+ if (newOnes.length > 0) {
169
+ const match = newOnes.find((ig) => ig.type === targetType);
170
+ detected = match || newOnes[0];
171
+ break;
172
+ }
123
173
  }
124
174
  catch { }
175
+ const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
176
+ pollSpinner.text = chalk_1.default.gray(`Esperando conexion... ${elapsed}s (Enter para saltar)`);
125
177
  }
126
- resolve();
127
- }, POLL_INTERVAL * MAX_POLLS + 1000);
128
- });
129
- for (let i = 0; i < MAX_POLLS; i++) {
130
- await Promise.race([
131
- new Promise(r => setTimeout(r, POLL_INTERVAL)),
132
- skipPromise,
133
- ]);
134
- if (skipped)
135
- break;
136
- try {
137
- const wkRes = await axios_1.default.get(`${baseUrl}/api/workspace/${ctx.workspaceId}`, { headers });
138
- // La API retorna un array [{ integrations: [...] }]
139
- const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
140
- const integrations = wsData?.integrations || [];
141
- // Buscar integraciones nuevas (cualquier ID que no existia antes)
142
- const newOnes = integrations.filter((ig) => !prevIntegrationIds.has(ig.id));
143
- if (newOnes.length > 0) {
144
- // Preferir la que coincida con el tipo solicitado
145
- const targetType = channelType === 'whatsapp_business' ? 'whatsapp' : channelType;
146
- const matchingNew = newOnes.find((ig) => ig.type === targetType);
147
- detected = matchingNew || newOnes[0];
148
- break;
178
+ if (detected) {
179
+ const detectedName = getIntegrationLabel(detected);
180
+ pollSpinner.succeed(chalk_1.default.hex('#4ade80')(`Conexion detectada: ${detectedName}`));
181
+ channels.push({
182
+ channel: CHANNEL_AGENT_MAP[channelType],
183
+ key: detected.id,
184
+ multianswer: false,
185
+ });
186
+ console.log(chalk_1.default.hex('#4ade80')(' Canal asociado correctamente.'));
187
+ }
188
+ else {
189
+ pollSpinner.warn(chalk_1.default.hex('#FFA726')(skipped ? 'Omitido.' : 'Timeout: no se detecto conexion.'));
190
+ const { action } = await inquirer_1.default.prompt([{
191
+ type: 'list', name: 'action',
192
+ message: 'Que deseas hacer?',
193
+ choices: [
194
+ { name: 'Ingresar ID manualmente', value: 'manual' },
195
+ { name: 'Saltar', value: 'skip' },
196
+ ],
197
+ }]);
198
+ if (action === 'manual') {
199
+ const { key } = await inquirer_1.default.prompt([
200
+ { type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v) => v.length > 0 || 'Requerido' },
201
+ ]);
202
+ channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
203
+ }
149
204
  }
150
- }
151
- catch {
152
- // Ignorar errores de poll individual
153
- }
154
- const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
155
- pollSpinner.text = chalk_1.default.gray(`Esperando conexion... ${elapsed}s (Enter para saltar)`);
156
- }
157
- if (detected) {
158
- const detectedName = detected.aliasName || detected.cellphoneNumberFormat || detected.cellphoneNumber || detected.srcName || detected.id;
159
- pollSpinner.succeed(chalk_1.default.hex('#4ade80')(`Conexion detectada: ${detectedName} (${channelLabel})`));
160
- const { associate } = await inquirer_1.default.prompt([{
161
- type: 'confirm', name: 'associate',
162
- message: 'Deseas asociar este canal al agente?', default: true,
163
- }]);
164
- if (associate) {
165
- channels.push({
166
- channel: CHANNEL_AGENT_MAP[channelType],
167
- key: detected.id,
168
- multianswer: false,
169
- });
170
- console.log(chalk_1.default.hex('#4ade80')(' Canal asociado correctamente.'));
171
- }
172
- }
173
- else {
174
- pollSpinner.warn(chalk_1.default.hex('#FFA726')(skipped ? 'Polling omitido.' : 'Timeout: no se detecto una conexion.'));
175
- const { action } = await inquirer_1.default.prompt([{
176
- type: 'list', name: 'action',
177
- message: 'Que deseas hacer?',
178
- choices: [
179
- { name: 'Ingresar numero/ID manualmente', value: 'manual' },
180
- { name: 'Saltar este paso', value: 'skip' },
181
- ],
182
- }]);
183
- if (action === 'manual') {
184
- const { key } = await inquirer_1.default.prompt([
185
- { type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v) => v.length > 0 || 'Requerido' },
186
- ]);
187
- channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
188
205
  }
189
206
  }
190
- }
191
- catch (err) {
192
- spinner.fail(chalk_1.default.hex('#EF5350')(`Error al generar link: ${err instanceof Error ? err.message : err}`));
193
- const { tryManual } = await inquirer_1.default.prompt([{
194
- type: 'confirm', name: 'tryManual',
195
- message: 'Deseas ingresar el numero manualmente?', default: false,
196
- }]);
197
- if (tryManual) {
198
- const { key } = await inquirer_1.default.prompt([
199
- { type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v) => v.length > 0 || 'Requerido' },
200
- ]);
201
- channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
207
+ catch (err) {
208
+ spinner.fail(chalk_1.default.hex('#EF5350')(`Error: ${err instanceof Error ? err.message : err}`));
202
209
  }
203
210
  }
204
211
  const { more } = await inquirer_1.default.prompt([{ type: 'confirm', name: 'more', message: 'Conectar otro canal?', default: false }]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plazbot-cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "CLI para Plazbot SDK",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -28,7 +28,8 @@ export const aiConfigCommand = new Command('ai-config')
28
28
 
29
29
  const spinner = createSpinner('Cargando agente...');
30
30
  spinner.start();
31
- const agentData = await agent.getAgentById({ id: agentId });
31
+ const _res: any = await agent.getAgentById({ id: agentId });
32
+ const agentData = _res.agent || _res.data || _res;
32
33
  spinner.stop();
33
34
 
34
35
  console.log(section('Configuracion de IA - ' + (agentData.name || agentId)));
@@ -45,7 +45,8 @@ export const chatCommand = new Command('chat')
45
45
  let agentInfo: any = null;
46
46
  try {
47
47
  process.stdout.write(chalk.gray(' Conectando con agente...'));
48
- agentInfo = await agent.getAgentById({ id: options.agentId });
48
+ const _res: any = await agent.getAgentById({ id: options.agentId });
49
+ agentInfo = _res.agent || _res.data || _res;
49
50
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
50
51
  } catch {
51
52
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
@@ -22,13 +22,14 @@ export const deleteCommand = new Command('delete')
22
22
  });
23
23
 
24
24
  // Obtener detalles del agente para mostrar información
25
- const agentDetails = await agent.getAgentById({ id: agentId });
26
-
25
+ const response: any = await agent.getAgentById({ id: agentId });
26
+ const agentDetails = response.agent || response.data || response;
27
+
27
28
  logger.warning('\nVas a eliminar el siguiente agente:');
28
29
  logger.divider();
29
- logger.info(`ID: ${agentDetails.id}`);
30
- logger.info(`Nombre: ${agentDetails.name}`);
31
- logger.info(`Descripción: ${agentDetails.description}`);
30
+ logger.info(`ID: ${agentDetails.id || agentId}`);
31
+ logger.info(`Nombre: ${agentDetails.name || '—'}`);
32
+ logger.info(`Descripcion: ${agentDetails.description || '—'}`);
32
33
  logger.divider();
33
34
 
34
35
  // Crear interfaz para confirmación
@@ -22,8 +22,9 @@ export const enableCommand = new Command('enable-widget')
22
22
  });
23
23
 
24
24
  // Obtener estado actual del agente
25
- const agentDetails = await agent.getAgentById({ id: agentId });
26
-
25
+ const _res: any = await agent.getAgentById({ id: agentId });
26
+ const agentDetails = _res.agent || _res.data || _res;
27
+
27
28
  logger.title('Estado actual del widget');
28
29
  logger.label('Agente', agentDetails.name);
29
30
 
@@ -25,7 +25,8 @@ filesGroup.command('list')
25
25
 
26
26
  const spinner = createSpinner('Cargando archivos...');
27
27
  spinner.start();
28
- const agentData = await agent.getAgentById({ id: agentId });
28
+ const _res: any = await agent.getAgentById({ id: agentId });
29
+ const agentData = _res.agent || _res.data || _res;
29
30
  spinner.stop();
30
31
 
31
32
  const files = (agentData as any).files || [];
@@ -193,7 +193,8 @@ export const monitorCommand = new Command('monitor')
193
193
  zone: effectiveZone,
194
194
  ...(options.dev && { customUrl: 'http://localhost:5090' }),
195
195
  });
196
- const info: any = await agent.getAgentById({ id: options.agentId });
196
+ const _res: any = await agent.getAgentById({ id: options.agentId });
197
+ const info = _res.agent || _res.data || _res;
197
198
  if (info?.name) agentName = info.name;
198
199
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
199
200
  } catch {
@@ -56,7 +56,8 @@ setGroup.command('instructions')
56
56
  // Cargar instrucciones actuales
57
57
  const loadSpinner = createSpinner('Cargando...');
58
58
  loadSpinner.start();
59
- const agentData = await agent.getAgentById({ id: agentId });
59
+ const _res1: any = await agent.getAgentById({ id: agentId });
60
+ const agentData = _res1.agent || _res1.data || _res1;
60
61
  loadSpinner.stop();
61
62
 
62
63
  const current = agentData.instructions || {};
@@ -110,7 +111,8 @@ setGroup.command('persona')
110
111
 
111
112
  const loadSpinner = createSpinner('Cargando...');
112
113
  loadSpinner.start();
113
- const agentData = await agent.getAgentById({ id: agentId });
114
+ const _res2: any = await agent.getAgentById({ id: agentId });
115
+ const agentData = _res2.agent || _res2.data || _res2;
114
116
  loadSpinner.stop();
115
117
 
116
118
  const current = agentData.person || {};
@@ -22,7 +22,8 @@ export const toolsCommand = new Command('tools')
22
22
 
23
23
  const spinner = createSpinner('Cargando agente...');
24
24
  spinner.start();
25
- const agentData = await agent.getAgentById({ id: agentId });
25
+ const _res: any = await agent.getAgentById({ id: agentId });
26
+ const agentData = _res.agent || _res.data || _res;
26
27
  spinner.stop();
27
28
 
28
29
  console.log(section('Tool Calling - ' + (agentData.name || agentId)));
@@ -122,6 +122,25 @@ const CHANNEL_AGENT_MAP: Record<string, string> = {
122
122
  facebook: 'facebook',
123
123
  };
124
124
 
125
+ function getIntegrationLabel(ig: any): string {
126
+ if (ig.type === 'whatsapp') {
127
+ return ig.aliasName
128
+ ? `${ig.aliasName} (${ig.cellphoneNumberFormat || ig.cellphoneNumber || ''})`
129
+ : ig.cellphoneNumberFormat || ig.cellphoneNumber || ig.id;
130
+ }
131
+ return ig.aliasName || ig.srcName || ig.id;
132
+ }
133
+
134
+ async function fetchWorkspaceIntegrations(baseUrl: string, workspaceId: string, headers: any): Promise<any[]> {
135
+ try {
136
+ const wkRes = await axios.get(`${baseUrl}/api/workspace/${workspaceId}`, { headers });
137
+ const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
138
+ return wsData?.integrations || [];
139
+ } catch {
140
+ return [];
141
+ }
142
+ }
143
+
125
144
  async function connectChannelFlow(ctx: WizardContext): Promise<AgentChannel[]> {
126
145
  const channels: AgentChannel[] = [];
127
146
  const baseUrl = getBaseUrl(ctx.zone, ctx.dev);
@@ -141,165 +160,153 @@ async function connectChannelFlow(ctx: WizardContext): Promise<AgentChannel[]> {
141
160
  }]);
142
161
 
143
162
  const channelLabel = CHANNEL_CHOICES.find(c => c.value === channelType)?.name || channelType;
163
+ const targetType = channelType === 'whatsapp_business' ? 'whatsapp' : channelType;
164
+
165
+ // Cargar integraciones existentes del tipo seleccionado
166
+ const loadSpinner = ora({ text: chalk.gray('Cargando integraciones...'), spinner: 'dots', color: 'green' }).start();
167
+ const allIntegrations = await fetchWorkspaceIntegrations(baseUrl, ctx.workspaceId, headers);
168
+ const existing = allIntegrations.filter((ig: any) => ig.type === targetType);
169
+ loadSpinner.stop();
170
+
171
+ let selectedIntegration: any = null;
172
+
173
+ if (existing.length > 0) {
174
+ // Mostrar integraciones existentes + opcion de agregar nueva
175
+ const choices = existing.map((ig: any) => ({
176
+ name: `${getIntegrationLabel(ig)}`,
177
+ value: ig.id,
178
+ }));
179
+ choices.push(new (inquirer as any).Separator());
180
+ choices.push({ name: chalk.hex('#4ade80')('+ Agregar uno nuevo (generar link)'), value: '__new__' });
181
+
182
+ const { selected } = await (inquirer as any).prompt([{
183
+ type: 'list',
184
+ name: 'selected',
185
+ message: `Integraciones de ${channelLabel} disponibles:`,
186
+ choices,
187
+ }]);
144
188
 
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;
189
+ if (selected !== '__new__') {
190
+ // Selecciono una existente
191
+ selectedIntegration = existing.find((ig: any) => ig.id === selected);
192
+ const label = getIntegrationLabel(selectedIntegration);
193
+ channels.push({
194
+ channel: CHANNEL_AGENT_MAP[channelType],
195
+ key: selectedIntegration.id,
196
+ multianswer: false,
197
+ });
198
+ console.log(chalk.hex('#4ade80')(` Canal asociado: ${label}`));
170
199
  }
200
+ }
201
+
202
+ // Si no hay existentes o eligio "Agregar nuevo" → generar link de onboarding
203
+ if (!selectedIntegration) {
204
+ const spinner = ora({ text: chalk.gray('Generando link de conexion...'), spinner: 'dots', color: 'green' }).start();
171
205
 
172
- const shortUrl = linkRes.data.data?.shortUrl || '';
173
- spinner.stop();
174
-
175
- // Mostrar link
176
- console.log();
177
- console.log(chalk.hex('#4CAF50')(' ─'.repeat(32)));
178
- console.log(chalk.bold(` Envia este link para conectar ${channelLabel}:`));
179
- console.log();
180
- console.log(' ' + chalk.hex('#22d3ee')(shortUrl));
181
- console.log();
182
- console.log(chalk.hex('#4CAF50')(' ─'.repeat(32)));
183
- console.log();
184
-
185
- // Snapshot de integraciones actuales
186
- let prevIntegrationIds: Set<string> = new Set();
187
206
  try {
188
- const wkRes = await axios.get(`${baseUrl}/api/workspace/${ctx.workspaceId}`, { headers });
189
- // La API retorna un array [{ integrations: [...] }]
190
- const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
191
- const integrations = wsData?.integrations || [];
192
- prevIntegrationIds = new Set(integrations.map((i: any) => i.id));
193
- } catch {
194
- // Si falla, se parte de vacio
195
- }
207
+ const linkRes = await axios.post(
208
+ `${baseUrl}/api/workspace/${ctx.workspaceId}/onboarding-link`,
209
+ { type: channelType },
210
+ { headers }
211
+ );
212
+
213
+ if (!linkRes.data?.success) {
214
+ spinner.fail(chalk.hex('#EF5350')(`Error: ${linkRes.data?.message || 'No se pudo generar el link'}`));
215
+ } else {
216
+ const shortUrl = linkRes.data.data?.shortUrl || '';
217
+ spinner.stop();
218
+
219
+ // Mostrar link
220
+ console.log();
221
+ console.log(chalk.hex('#4CAF50')(' ─'.repeat(32)));
222
+ console.log(chalk.bold(` Envia este link para conectar ${channelLabel}:`));
223
+ console.log();
224
+ console.log(' ' + chalk.hex('#22d3ee')(shortUrl));
225
+ console.log();
226
+ console.log(chalk.hex('#4CAF50')(' ─'.repeat(32)));
227
+ console.log();
228
+
229
+ // Guardar IDs actuales para detectar nuevos
230
+ const prevIds = new Set(allIntegrations.map((i: any) => i.id));
231
+
232
+ // Polling para detectar nueva integracion (Enter para saltar)
233
+ const pollSpinner = ora({ text: chalk.gray('Esperando conexion... (Enter para saltar)'), spinner: 'dots', color: 'cyan' }).start();
234
+ const POLL_INTERVAL = 5000;
235
+ const MAX_POLLS = 60;
236
+ let detected: any = null;
237
+ let skipped = false;
238
+
239
+ // Escuchar Enter para saltar
240
+ const skipPromise = new Promise<void>(resolve => {
241
+ const onData = (data: Buffer) => {
242
+ if (data.toString().includes('\n') || data.toString().includes('\r')) {
243
+ skipped = true;
244
+ process.stdin.removeListener('data', onData);
245
+ if (wasRaw) process.stdin.setRawMode(false);
246
+ resolve();
247
+ }
248
+ };
249
+ const wasRaw = process.stdin.isRaw;
250
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
251
+ process.stdin.resume();
252
+ process.stdin.on('data', onData);
253
+ setTimeout(() => {
254
+ process.stdin.removeListener('data', onData);
255
+ if (process.stdin.isTTY && wasRaw !== undefined) {
256
+ try { process.stdin.setRawMode(wasRaw); } catch {}
257
+ }
258
+ resolve();
259
+ }, POLL_INTERVAL * MAX_POLLS + 1000);
260
+ });
196
261
 
197
- // Polling para detectar nueva integracion (Enter para saltar)
198
- const pollSpinner = ora({ text: chalk.gray('Esperando conexion... (Enter para saltar)'), spinner: 'dots', color: 'cyan' }).start();
199
- const POLL_INTERVAL = 5000;
200
- const MAX_POLLS = 60; // 5 minutos
201
- let detected: any = null;
202
- let skipped = false;
203
-
204
- // Escuchar Enter para saltar el polling
205
- const skipPromise = new Promise<void>(resolve => {
206
- const onData = (data: Buffer) => {
207
- if (data.toString().includes('\n') || data.toString().includes('\r')) {
208
- skipped = true;
209
- process.stdin.removeListener('data', onData);
210
- if (wasRaw) process.stdin.setRawMode(false);
211
- resolve();
212
- }
213
- };
214
- const wasRaw = process.stdin.isRaw;
215
- if (process.stdin.isTTY) process.stdin.setRawMode(true);
216
- process.stdin.resume();
217
- process.stdin.on('data', onData);
218
- // Limpiar si termina el polling naturalmente
219
- setTimeout(() => {
220
- process.stdin.removeListener('data', onData);
221
- if (process.stdin.isTTY && wasRaw !== undefined) {
222
- try { process.stdin.setRawMode(wasRaw); } catch {}
223
- }
224
- resolve();
225
- }, POLL_INTERVAL * MAX_POLLS + 1000);
226
- });
227
-
228
- for (let i = 0; i < MAX_POLLS; i++) {
229
- await Promise.race([
230
- new Promise(r => setTimeout(r, POLL_INTERVAL)),
231
- skipPromise,
232
- ]);
233
- if (skipped) break;
234
- try {
235
- const wkRes = await axios.get(`${baseUrl}/api/workspace/${ctx.workspaceId}`, { headers });
236
- // La API retorna un array [{ integrations: [...] }]
237
- const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
238
- const integrations = wsData?.integrations || [];
239
-
240
- // Buscar integraciones nuevas (cualquier ID que no existia antes)
241
- const newOnes = integrations.filter((ig: any) => !prevIntegrationIds.has(ig.id));
242
-
243
- if (newOnes.length > 0) {
244
- // Preferir la que coincida con el tipo solicitado
245
- const targetType = channelType === 'whatsapp_business' ? 'whatsapp' : channelType;
246
- const matchingNew = newOnes.find((ig: any) => ig.type === targetType);
247
- detected = matchingNew || newOnes[0];
248
- break;
262
+ for (let i = 0; i < MAX_POLLS; i++) {
263
+ await Promise.race([
264
+ new Promise(r => setTimeout(r, POLL_INTERVAL)),
265
+ skipPromise,
266
+ ]);
267
+ if (skipped) break;
268
+ try {
269
+ const freshIntegrations = await fetchWorkspaceIntegrations(baseUrl, ctx.workspaceId, headers);
270
+ const newOnes = freshIntegrations.filter((ig: any) => !prevIds.has(ig.id));
271
+ if (newOnes.length > 0) {
272
+ const match = newOnes.find((ig: any) => ig.type === targetType);
273
+ detected = match || newOnes[0];
274
+ break;
275
+ }
276
+ } catch {}
277
+ const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
278
+ pollSpinner.text = chalk.gray(`Esperando conexion... ${elapsed}s (Enter para saltar)`);
249
279
  }
250
- } catch {
251
- // Ignorar errores de poll individual
252
- }
253
- const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
254
- pollSpinner.text = chalk.gray(`Esperando conexion... ${elapsed}s (Enter para saltar)`);
255
- }
256
-
257
- if (detected) {
258
- const detectedName = detected.aliasName || detected.cellphoneNumberFormat || detected.cellphoneNumber || detected.srcName || detected.id;
259
- pollSpinner.succeed(chalk.hex('#4ade80')(`Conexion detectada: ${detectedName} (${channelLabel})`));
260
-
261
- const { associate } = await (inquirer as any).prompt([{
262
- type: 'confirm', name: 'associate',
263
- message: 'Deseas asociar este canal al agente?', default: true,
264
- }]);
265
280
 
266
- if (associate) {
267
- channels.push({
268
- channel: CHANNEL_AGENT_MAP[channelType],
269
- key: detected.id,
270
- multianswer: false,
271
- });
272
- console.log(chalk.hex('#4ade80')(' Canal asociado correctamente.'));
273
- }
274
- } else {
275
- pollSpinner.warn(chalk.hex('#FFA726')(skipped ? 'Polling omitido.' : 'Timeout: no se detecto una conexion.'));
276
- const { action } = await (inquirer as any).prompt([{
277
- type: 'list', name: 'action',
278
- message: 'Que deseas hacer?',
279
- choices: [
280
- { name: 'Ingresar numero/ID manualmente', value: 'manual' },
281
- { name: 'Saltar este paso', value: 'skip' },
282
- ],
283
- }]);
284
- if (action === 'manual') {
285
- const { key } = await (inquirer as any).prompt([
286
- { type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v: string) => v.length > 0 || 'Requerido' },
287
- ]);
288
- channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
281
+ if (detected) {
282
+ const detectedName = getIntegrationLabel(detected);
283
+ pollSpinner.succeed(chalk.hex('#4ade80')(`Conexion detectada: ${detectedName}`));
284
+ channels.push({
285
+ channel: CHANNEL_AGENT_MAP[channelType],
286
+ key: detected.id,
287
+ multianswer: false,
288
+ });
289
+ console.log(chalk.hex('#4ade80')(' Canal asociado correctamente.'));
290
+ } else {
291
+ pollSpinner.warn(chalk.hex('#FFA726')(skipped ? 'Omitido.' : 'Timeout: no se detecto conexion.'));
292
+ const { action } = await (inquirer as any).prompt([{
293
+ type: 'list', name: 'action',
294
+ message: 'Que deseas hacer?',
295
+ choices: [
296
+ { name: 'Ingresar ID manualmente', value: 'manual' },
297
+ { name: 'Saltar', value: 'skip' },
298
+ ],
299
+ }]);
300
+ if (action === 'manual') {
301
+ const { key } = await (inquirer as any).prompt([
302
+ { type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v: string) => v.length > 0 || 'Requerido' },
303
+ ]);
304
+ channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
305
+ }
306
+ }
289
307
  }
290
- }
291
-
292
- } catch (err) {
293
- spinner.fail(chalk.hex('#EF5350')(`Error al generar link: ${err instanceof Error ? err.message : err}`));
294
- const { tryManual } = await (inquirer as any).prompt([{
295
- type: 'confirm', name: 'tryManual',
296
- message: 'Deseas ingresar el numero manualmente?', default: false,
297
- }]);
298
- if (tryManual) {
299
- const { key } = await (inquirer as any).prompt([
300
- { type: 'input', name: 'key', message: `ID/numero del canal ${channelLabel}:`, validate: (v: string) => v.length > 0 || 'Requerido' },
301
- ]);
302
- channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
308
+ } catch (err) {
309
+ spinner.fail(chalk.hex('#EF5350')(`Error: ${err instanceof Error ? err.message : err}`));
303
310
  }
304
311
  }
305
312