plazbot-cli 0.2.8 → 0.2.10
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 +2 -0
- package/dist/commands/agent/wizard.js +148 -141
- package/package.json +1 -1
- package/src/commands/agent/create.ts +3 -0
- package/src/commands/agent/wizard.ts +158 -151
|
@@ -105,9 +105,11 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
105
105
|
}
|
|
106
106
|
console.log();
|
|
107
107
|
logger_1.logger.dim('Siguiente paso: plazbot agent chat -a ' + (result.agentId || '<agentId>'));
|
|
108
|
+
console.log();
|
|
108
109
|
if (options.dev) {
|
|
109
110
|
logger_1.logger.warning('Ambiente: desarrollo');
|
|
110
111
|
}
|
|
112
|
+
process.exit(0);
|
|
111
113
|
}
|
|
112
114
|
catch (error) {
|
|
113
115
|
const message = error instanceof Error ? error.message : 'Error desconocido al crear el agente';
|
|
@@ -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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
@@ -77,11 +77,14 @@ export const createCommand = new Command('create')
|
|
|
77
77
|
|
|
78
78
|
console.log();
|
|
79
79
|
logger.dim('Siguiente paso: plazbot agent chat -a ' + (result.agentId || '<agentId>'));
|
|
80
|
+
console.log();
|
|
80
81
|
|
|
81
82
|
if (options.dev) {
|
|
82
83
|
logger.warning('Ambiente: desarrollo');
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
process.exit(0);
|
|
87
|
+
|
|
85
88
|
} catch (error) {
|
|
86
89
|
const message = error instanceof Error ? error.message : 'Error desconocido al crear el agente';
|
|
87
90
|
logger.error(message);
|
|
@@ -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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|