plazbot-cli 0.2.23 → 0.2.25

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.
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.whatsappConnectCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const credentials_1 = require("../../utils/credentials");
12
+ const logger_1 = require("../../utils/logger");
13
+ const ui_1 = require("../../utils/ui");
14
+ function getBaseUrl(zone, dev) {
15
+ if (dev)
16
+ return 'http://localhost:5090';
17
+ return zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com';
18
+ }
19
+ function getIntegrationLabel(ig) {
20
+ if (ig.type === 'whatsapp') {
21
+ return ig.aliasName
22
+ ? `${ig.aliasName} (${ig.cellphoneNumberFormat || ig.cellphoneNumber || ''})`
23
+ : ig.cellphoneNumberFormat || ig.cellphoneNumber || ig.id;
24
+ }
25
+ return ig.aliasName || ig.srcName || ig.id;
26
+ }
27
+ async function fetchWorkspaceIntegrations(baseUrl, workspaceId, headers) {
28
+ try {
29
+ const wkRes = await axios_1.default.get(`${baseUrl}/api/workspace/${workspaceId}`, { headers });
30
+ const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
31
+ return wsData?.integrations || [];
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ }
37
+ async function activateIntegration(baseUrl, workspaceId, integrationId, headers) {
38
+ try {
39
+ const res = await axios_1.default.post(`${baseUrl}/api/workspace/${workspaceId}/integrations/${integrationId}/activate`, { isActive: true }, { headers });
40
+ return res.data?.success === true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ exports.whatsappConnectCommand = new commander_1.Command('connect')
47
+ .description('Conectar un numero de WhatsApp al workspace (sin crear agente)')
48
+ .option('--dev', 'Usar ambiente de desarrollo', false)
49
+ .option('--business', 'Conectar como WhatsApp Business', false)
50
+ .action(async (options) => {
51
+ try {
52
+ const credentials = await (0, credentials_1.getStoredCredentials)();
53
+ const baseUrl = getBaseUrl(credentials.zone, options.dev);
54
+ const headers = {
55
+ 'Authorization': `Bearer ${credentials.apiKey}`,
56
+ 'x-workspace-id': credentials.workspace,
57
+ 'Content-Type': 'application/json',
58
+ };
59
+ const channelType = options.business ? 'whatsapp_business' : 'whatsapp';
60
+ const channelLabel = options.business ? 'WhatsApp Business' : 'WhatsApp';
61
+ console.log((0, ui_1.section)(`Conectar ${channelLabel}`));
62
+ console.log();
63
+ // Cargar integraciones existentes
64
+ const loadSpinner = (0, ora_1.default)({ text: chalk_1.default.gray('Cargando integraciones existentes...'), spinner: 'dots', color: 'green' }).start();
65
+ const allIntegrations = await fetchWorkspaceIntegrations(baseUrl, credentials.workspace, headers);
66
+ const existing = allIntegrations.filter((ig) => ig.type === 'whatsapp');
67
+ loadSpinner.stop();
68
+ if (existing.length > 0) {
69
+ console.log(ui_1.theme.muted(` ${existing.length} numero(s) ya conectado(s):`));
70
+ existing.forEach((ig) => {
71
+ const label = getIntegrationLabel(ig);
72
+ const status = ig.isActive ? chalk_1.default.hex('#66BB6A')('activo') : chalk_1.default.hex('#EF5350')('inactivo');
73
+ console.log(` ${ui_1.theme.muted('•')} ${label} ${status}`);
74
+ });
75
+ console.log();
76
+ }
77
+ // Obtener o generar link de onboarding (mismo flujo que Plazbot Front)
78
+ const ONBOARDING_SHORT_DOMAIN = 'https://co.plzb.link';
79
+ const spinner = (0, ora_1.default)({ text: chalk_1.default.gray('Obteniendo link de conexion...'), spinner: 'dots', color: 'green' }).start();
80
+ let linkData = null;
81
+ // 1. Intentar obtener link existente (GET)
82
+ try {
83
+ const getRes = await axios_1.default.get(`${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link`, { headers, params: { type: channelType } });
84
+ linkData = getRes.data?.data ?? null;
85
+ }
86
+ catch {
87
+ linkData = null;
88
+ }
89
+ // 2. Si existe pero esta expirado, regenerar
90
+ if (linkData?.expiresAt && new Date(linkData.expiresAt) < new Date()) {
91
+ try {
92
+ const regenRes = await axios_1.default.post(`${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link/regenerate`, { type: channelType }, { headers });
93
+ linkData = regenRes.data?.data ?? null;
94
+ }
95
+ catch {
96
+ linkData = null;
97
+ }
98
+ }
99
+ // 3. Si no existe, generar uno nuevo (POST)
100
+ if (!linkData?.token) {
101
+ try {
102
+ const createRes = await axios_1.default.post(`${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link`, { type: channelType }, { headers });
103
+ linkData = createRes.data?.data ?? null;
104
+ }
105
+ catch {
106
+ linkData = null;
107
+ }
108
+ }
109
+ if (!linkData?.token) {
110
+ spinner.fail(chalk_1.default.hex('#EF5350')('No se pudo obtener el link de conexion'));
111
+ process.exit(1);
112
+ }
113
+ const shortUrl = `${ONBOARDING_SHORT_DOMAIN}/${linkData.token}`;
114
+ const expiresAt = linkData.expiresAt ? new Date(linkData.expiresAt).toLocaleDateString('es-ES', { day: 'numeric', month: 'long', year: 'numeric' }) : '';
115
+ spinner.stop();
116
+ // Mostrar link
117
+ console.log(chalk_1.default.hex('#25D366')(' ┌' + '─'.repeat(54) + '┐'));
118
+ console.log(chalk_1.default.hex('#25D366')(' │') + chalk_1.default.bold(` Abre este link en tu navegador:`).padEnd(64) + chalk_1.default.hex('#25D366')('│'));
119
+ console.log(chalk_1.default.hex('#25D366')(' │') + ''.padEnd(54) + chalk_1.default.hex('#25D366')('│'));
120
+ console.log(chalk_1.default.hex('#25D366')(' │') + chalk_1.default.hex('#22d3ee')(` ${shortUrl}`).padEnd(64) + chalk_1.default.hex('#25D366')('│'));
121
+ console.log(chalk_1.default.hex('#25D366')(' │') + ''.padEnd(54) + chalk_1.default.hex('#25D366')('│'));
122
+ console.log(chalk_1.default.hex('#25D366')(' │') + chalk_1.default.gray(' Escanea el codigo QR desde tu WhatsApp').padEnd(64) + chalk_1.default.hex('#25D366')('│'));
123
+ if (expiresAt) {
124
+ console.log(chalk_1.default.hex('#25D366')(' │') + chalk_1.default.gray(` Expira: ${expiresAt}`).padEnd(64) + chalk_1.default.hex('#25D366')('│'));
125
+ }
126
+ console.log(chalk_1.default.hex('#25D366')(' └' + '─'.repeat(54) + '┘'));
127
+ console.log();
128
+ // Guardar IDs actuales para detectar nuevos
129
+ const prevIds = new Set(allIntegrations.map((i) => i.id));
130
+ // Polling para detectar nueva integracion
131
+ const pollSpinner = (0, ora_1.default)({ text: chalk_1.default.gray('Esperando conexion... (Ctrl+C para cancelar)'), spinner: 'dots', color: 'cyan' }).start();
132
+ const POLL_INTERVAL = 5000;
133
+ const MAX_POLLS = 60; // 5 minutos
134
+ let detected = null;
135
+ for (let i = 0; i < MAX_POLLS; i++) {
136
+ await new Promise(r => setTimeout(r, POLL_INTERVAL));
137
+ try {
138
+ const freshIntegrations = await fetchWorkspaceIntegrations(baseUrl, credentials.workspace, headers);
139
+ const newOnes = freshIntegrations.filter((ig) => !prevIds.has(ig.id));
140
+ if (newOnes.length > 0) {
141
+ const match = newOnes.find((ig) => ig.type === 'whatsapp');
142
+ detected = match || newOnes[0];
143
+ break;
144
+ }
145
+ }
146
+ catch { }
147
+ const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
148
+ pollSpinner.text = chalk_1.default.gray(`Esperando conexion... ${elapsed}s (Ctrl+C para cancelar)`);
149
+ }
150
+ if (detected) {
151
+ const detectedName = getIntegrationLabel(detected);
152
+ pollSpinner.succeed(chalk_1.default.hex('#4ade80')(`Conexion detectada: ${detectedName}`));
153
+ // Activar la integracion
154
+ const actSpinner = (0, ora_1.default)({ text: chalk_1.default.gray('Activando integracion...'), spinner: 'dots', color: 'green' }).start();
155
+ const activated = await activateIntegration(baseUrl, credentials.workspace, detected.id, headers);
156
+ if (activated) {
157
+ actSpinner.succeed(chalk_1.default.hex('#4ade80')('Integracion activada correctamente'));
158
+ }
159
+ else {
160
+ actSpinner.warn(chalk_1.default.hex('#FFA726')('No se pudo activar automaticamente. Activala desde la plataforma.'));
161
+ }
162
+ console.log();
163
+ console.log(ui_1.theme.success(' WhatsApp conectado exitosamente'));
164
+ console.log(ui_1.theme.muted(` Numero: ${detectedName}`));
165
+ console.log();
166
+ console.log(ui_1.theme.muted(' Ahora puedes:'));
167
+ console.log(ui_1.theme.muted(' plazbot whatsapp send-message Enviar un mensaje'));
168
+ console.log(ui_1.theme.muted(' plazbot whatsapp chat <phone> Chat interactivo'));
169
+ console.log(ui_1.theme.muted(' plazbot whatsapp assign <phone> <agentId> Asignar a un agente'));
170
+ console.log();
171
+ }
172
+ else {
173
+ pollSpinner.warn(chalk_1.default.hex('#FFA726')('Timeout: no se detecto conexion en 5 minutos.'));
174
+ console.log(ui_1.theme.muted(' Puedes volver a intentar con: plazbot whatsapp connect'));
175
+ console.log();
176
+ }
177
+ }
178
+ catch (error) {
179
+ const message = error instanceof Error ? error.message : 'Error desconocido';
180
+ logger_1.logger.error(message);
181
+ process.exit(1);
182
+ }
183
+ });
@@ -8,10 +8,12 @@ const register_webhook_1 = require("./register-webhook");
8
8
  const delete_webhook_1 = require("./delete-webhook");
9
9
  const broadcast_1 = require("./broadcast");
10
10
  const chat_1 = require("./chat");
11
+ const connect_1 = require("./connect");
11
12
  const widget_1 = require("./widget");
12
13
  const channels_1 = require("./channels");
13
14
  exports.whatsappCommands = new commander_1.Command('whatsapp')
14
15
  .description('Comandos de WhatsApp: mensajes, templates, broadcast y mas')
16
+ .addCommand(connect_1.whatsappConnectCommand)
15
17
  .addCommand(send_1.sendMessageCommand)
16
18
  .addCommand(send_template_1.sendTemplateCommand)
17
19
  .addCommand(register_webhook_1.registerWebhookCommand)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plazbot-cli",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "description": "CLI para Plazbot SDK",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -0,0 +1,208 @@
1
+ import { Command } from 'commander';
2
+ import axios from 'axios';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+ import { getStoredCredentials } from '../../utils/credentials';
6
+ import { logger } from '../../utils/logger';
7
+ import { theme, section } from '../../utils/ui';
8
+
9
+ function getBaseUrl(zone: string, dev: boolean): string {
10
+ if (dev) return 'http://localhost:5090';
11
+ return zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com';
12
+ }
13
+
14
+ function getIntegrationLabel(ig: any): string {
15
+ if (ig.type === 'whatsapp') {
16
+ return ig.aliasName
17
+ ? `${ig.aliasName} (${ig.cellphoneNumberFormat || ig.cellphoneNumber || ''})`
18
+ : ig.cellphoneNumberFormat || ig.cellphoneNumber || ig.id;
19
+ }
20
+ return ig.aliasName || ig.srcName || ig.id;
21
+ }
22
+
23
+ async function fetchWorkspaceIntegrations(baseUrl: string, workspaceId: string, headers: any): Promise<any[]> {
24
+ try {
25
+ const wkRes = await axios.get(`${baseUrl}/api/workspace/${workspaceId}`, { headers });
26
+ const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
27
+ return wsData?.integrations || [];
28
+ } catch {
29
+ return [];
30
+ }
31
+ }
32
+
33
+ async function activateIntegration(baseUrl: string, workspaceId: string, integrationId: string, headers: any): Promise<boolean> {
34
+ try {
35
+ const res = await axios.post(
36
+ `${baseUrl}/api/workspace/${workspaceId}/integrations/${integrationId}/activate`,
37
+ { isActive: true },
38
+ { headers }
39
+ );
40
+ return res.data?.success === true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ export const whatsappConnectCommand = new Command('connect')
47
+ .description('Conectar un numero de WhatsApp al workspace (sin crear agente)')
48
+ .option('--dev', 'Usar ambiente de desarrollo', false)
49
+ .option('--business', 'Conectar como WhatsApp Business', false)
50
+ .action(async (options: any) => {
51
+ try {
52
+ const credentials = await getStoredCredentials();
53
+ const baseUrl = getBaseUrl(credentials.zone, options.dev);
54
+ const headers = {
55
+ 'Authorization': `Bearer ${credentials.apiKey}`,
56
+ 'x-workspace-id': credentials.workspace,
57
+ 'Content-Type': 'application/json',
58
+ };
59
+
60
+ const channelType = options.business ? 'whatsapp_business' : 'whatsapp';
61
+ const channelLabel = options.business ? 'WhatsApp Business' : 'WhatsApp';
62
+
63
+ console.log(section(`Conectar ${channelLabel}`));
64
+ console.log();
65
+
66
+ // Cargar integraciones existentes
67
+ const loadSpinner = ora({ text: chalk.gray('Cargando integraciones existentes...'), spinner: 'dots', color: 'green' }).start();
68
+ const allIntegrations = await fetchWorkspaceIntegrations(baseUrl, credentials.workspace, headers);
69
+ const existing = allIntegrations.filter((ig: any) => ig.type === 'whatsapp');
70
+ loadSpinner.stop();
71
+
72
+ if (existing.length > 0) {
73
+ console.log(theme.muted(` ${existing.length} numero(s) ya conectado(s):`));
74
+ existing.forEach((ig: any) => {
75
+ const label = getIntegrationLabel(ig);
76
+ const status = ig.isActive ? chalk.hex('#66BB6A')('activo') : chalk.hex('#EF5350')('inactivo');
77
+ console.log(` ${theme.muted('•')} ${label} ${status}`);
78
+ });
79
+ console.log();
80
+ }
81
+
82
+ // Obtener o generar link de onboarding (mismo flujo que Plazbot Front)
83
+ const ONBOARDING_SHORT_DOMAIN = 'https://co.plzb.link';
84
+ const spinner = ora({ text: chalk.gray('Obteniendo link de conexion...'), spinner: 'dots', color: 'green' }).start();
85
+
86
+ let linkData: any = null;
87
+
88
+ // 1. Intentar obtener link existente (GET)
89
+ try {
90
+ const getRes = await axios.get(
91
+ `${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link`,
92
+ { headers, params: { type: channelType } }
93
+ );
94
+ linkData = getRes.data?.data ?? null;
95
+ } catch {
96
+ linkData = null;
97
+ }
98
+
99
+ // 2. Si existe pero esta expirado, regenerar
100
+ if (linkData?.expiresAt && new Date(linkData.expiresAt) < new Date()) {
101
+ try {
102
+ const regenRes = await axios.post(
103
+ `${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link/regenerate`,
104
+ { type: channelType },
105
+ { headers }
106
+ );
107
+ linkData = regenRes.data?.data ?? null;
108
+ } catch {
109
+ linkData = null;
110
+ }
111
+ }
112
+
113
+ // 3. Si no existe, generar uno nuevo (POST)
114
+ if (!linkData?.token) {
115
+ try {
116
+ const createRes = await axios.post(
117
+ `${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link`,
118
+ { type: channelType },
119
+ { headers }
120
+ );
121
+ linkData = createRes.data?.data ?? null;
122
+ } catch {
123
+ linkData = null;
124
+ }
125
+ }
126
+
127
+ if (!linkData?.token) {
128
+ spinner.fail(chalk.hex('#EF5350')('No se pudo obtener el link de conexion'));
129
+ process.exit(1);
130
+ }
131
+
132
+ const shortUrl = `${ONBOARDING_SHORT_DOMAIN}/${linkData.token}`;
133
+ const expiresAt = linkData.expiresAt ? new Date(linkData.expiresAt).toLocaleDateString('es-ES', { day: 'numeric', month: 'long', year: 'numeric' }) : '';
134
+ spinner.stop();
135
+
136
+ // Mostrar link
137
+ console.log(chalk.hex('#25D366')(' ┌' + '─'.repeat(54) + '┐'));
138
+ console.log(chalk.hex('#25D366')(' │') + chalk.bold(` Abre este link en tu navegador:`).padEnd(64) + chalk.hex('#25D366')('│'));
139
+ console.log(chalk.hex('#25D366')(' │') + ''.padEnd(54) + chalk.hex('#25D366')('│'));
140
+ console.log(chalk.hex('#25D366')(' │') + chalk.hex('#22d3ee')(` ${shortUrl}`).padEnd(64) + chalk.hex('#25D366')('│'));
141
+ console.log(chalk.hex('#25D366')(' │') + ''.padEnd(54) + chalk.hex('#25D366')('│'));
142
+ console.log(chalk.hex('#25D366')(' │') + chalk.gray(' Escanea el codigo QR desde tu WhatsApp').padEnd(64) + chalk.hex('#25D366')('│'));
143
+ if (expiresAt) {
144
+ console.log(chalk.hex('#25D366')(' │') + chalk.gray(` Expira: ${expiresAt}`).padEnd(64) + chalk.hex('#25D366')('│'));
145
+ }
146
+ console.log(chalk.hex('#25D366')(' └' + '─'.repeat(54) + '┘'));
147
+ console.log();
148
+
149
+ // Guardar IDs actuales para detectar nuevos
150
+ const prevIds = new Set(allIntegrations.map((i: any) => i.id));
151
+
152
+ // Polling para detectar nueva integracion
153
+ const pollSpinner = ora({ text: chalk.gray('Esperando conexion... (Ctrl+C para cancelar)'), spinner: 'dots', color: 'cyan' }).start();
154
+ const POLL_INTERVAL = 5000;
155
+ const MAX_POLLS = 60; // 5 minutos
156
+ let detected: any = null;
157
+
158
+ for (let i = 0; i < MAX_POLLS; i++) {
159
+ await new Promise(r => setTimeout(r, POLL_INTERVAL));
160
+
161
+ try {
162
+ const freshIntegrations = await fetchWorkspaceIntegrations(baseUrl, credentials.workspace, headers);
163
+ const newOnes = freshIntegrations.filter((ig: any) => !prevIds.has(ig.id));
164
+ if (newOnes.length > 0) {
165
+ const match = newOnes.find((ig: any) => ig.type === 'whatsapp');
166
+ detected = match || newOnes[0];
167
+ break;
168
+ }
169
+ } catch {}
170
+
171
+ const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
172
+ pollSpinner.text = chalk.gray(`Esperando conexion... ${elapsed}s (Ctrl+C para cancelar)`);
173
+ }
174
+
175
+ if (detected) {
176
+ const detectedName = getIntegrationLabel(detected);
177
+ pollSpinner.succeed(chalk.hex('#4ade80')(`Conexion detectada: ${detectedName}`));
178
+
179
+ // Activar la integracion
180
+ const actSpinner = ora({ text: chalk.gray('Activando integracion...'), spinner: 'dots', color: 'green' }).start();
181
+ const activated = await activateIntegration(baseUrl, credentials.workspace, detected.id, headers);
182
+ if (activated) {
183
+ actSpinner.succeed(chalk.hex('#4ade80')('Integracion activada correctamente'));
184
+ } else {
185
+ actSpinner.warn(chalk.hex('#FFA726')('No se pudo activar automaticamente. Activala desde la plataforma.'));
186
+ }
187
+
188
+ console.log();
189
+ console.log(theme.success(' WhatsApp conectado exitosamente'));
190
+ console.log(theme.muted(` Numero: ${detectedName}`));
191
+ console.log();
192
+ console.log(theme.muted(' Ahora puedes:'));
193
+ console.log(theme.muted(' plazbot whatsapp send-message Enviar un mensaje'));
194
+ console.log(theme.muted(' plazbot whatsapp chat <phone> Chat interactivo'));
195
+ console.log(theme.muted(' plazbot whatsapp assign <phone> <agentId> Asignar a un agente'));
196
+ console.log();
197
+ } else {
198
+ pollSpinner.warn(chalk.hex('#FFA726')('Timeout: no se detecto conexion en 5 minutos.'));
199
+ console.log(theme.muted(' Puedes volver a intentar con: plazbot whatsapp connect'));
200
+ console.log();
201
+ }
202
+
203
+ } catch (error) {
204
+ const message = error instanceof Error ? error.message : 'Error desconocido';
205
+ logger.error(message);
206
+ process.exit(1);
207
+ }
208
+ });
@@ -5,11 +5,13 @@ import { registerWebhookCommand } from './register-webhook';
5
5
  import { deleteWebhookCommand } from './delete-webhook';
6
6
  import { broadcastCommand } from './broadcast';
7
7
  import { whatsappChatCommand } from './chat';
8
+ import { whatsappConnectCommand } from './connect';
8
9
  import { widgetCommand } from './widget';
9
10
  import { channelsCommand, assignCommand } from './channels';
10
11
 
11
12
  export const whatsappCommands = new Command('whatsapp')
12
13
  .description('Comandos de WhatsApp: mensajes, templates, broadcast y mas')
14
+ .addCommand(whatsappConnectCommand)
13
15
  .addCommand(sendMessageCommand)
14
16
  .addCommand(sendTemplateCommand)
15
17
  .addCommand(registerWebhookCommand)