plazbot-cli 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +38 -2
  2. package/dist/commands/agent/export.js +25 -2
  3. package/dist/commands/auth/browser-login.js +121 -0
  4. package/dist/commands/auth/index.js +5 -1
  5. package/dist/commands/auth/login.js +1 -3
  6. package/dist/commands/auth/logout.js +4 -3
  7. package/dist/commands/auth/status.js +8 -5
  8. package/dist/commands/auth/workspace.js +73 -0
  9. package/dist/commands/whatsapp/connect-flow.js +137 -0
  10. package/dist/commands/whatsapp/connect.js +26 -160
  11. package/dist/commands/whatsapp/index.js +4 -0
  12. package/dist/commands/whatsapp/setup.js +242 -0
  13. package/dist/studio/components/App.js +1 -1
  14. package/dist/studio/runOneShot.js +2 -2
  15. package/dist/studio/runRepl.js +1 -1
  16. package/dist/utils/agent-errors.js +2 -2
  17. package/dist/utils/banner.js +10 -9
  18. package/dist/utils/credentials.js +31 -3
  19. package/dist/utils/open-browser.js +17 -0
  20. package/package.json +14 -6
  21. package/.claude/settings.local.json +0 -15
  22. package/CLAUDE.md +0 -121
  23. package/agent.config.json +0 -21
  24. package/dist/studio/components/AgentPanel.js +0 -35
  25. package/dist/studio/render/json.js +0 -16
  26. package/dist/studio/whatsapp/api.js +0 -63
  27. package/dist/studio/whatsapp/polling.js +0 -77
  28. package/dist/studio/whatsapp/types.js +0 -31
  29. package/src/cli.ts +0 -57
  30. package/src/commands/agent/ai-config.ts +0 -164
  31. package/src/commands/agent/chat.ts +0 -225
  32. package/src/commands/agent/copy.ts +0 -46
  33. package/src/commands/agent/create.ts +0 -103
  34. package/src/commands/agent/delete.ts +0 -71
  35. package/src/commands/agent/enable-widget.ts +0 -67
  36. package/src/commands/agent/export.ts +0 -408
  37. package/src/commands/agent/files.ts +0 -171
  38. package/src/commands/agent/get.ts +0 -233
  39. package/src/commands/agent/index.ts +0 -55
  40. package/src/commands/agent/list.ts +0 -58
  41. package/src/commands/agent/monitor.ts +0 -375
  42. package/src/commands/agent/on-message.ts +0 -82
  43. package/src/commands/agent/set.ts +0 -149
  44. package/src/commands/agent/templates.ts +0 -892
  45. package/src/commands/agent/tools.ts +0 -168
  46. package/src/commands/agent/update.ts +0 -61
  47. package/src/commands/agent/validate.ts +0 -135
  48. package/src/commands/agent/wizard.ts +0 -756
  49. package/src/commands/auth/index.ts +0 -10
  50. package/src/commands/auth/login.ts +0 -72
  51. package/src/commands/auth/logout.ts +0 -28
  52. package/src/commands/auth/status.ts +0 -28
  53. package/src/commands/portal/add-agent.ts +0 -66
  54. package/src/commands/portal/add-link.ts +0 -47
  55. package/src/commands/portal/clear-links.ts +0 -33
  56. package/src/commands/portal/create.ts +0 -58
  57. package/src/commands/portal/delete.ts +0 -70
  58. package/src/commands/portal/get.ts +0 -77
  59. package/src/commands/portal/index.ts +0 -32
  60. package/src/commands/portal/list.ts +0 -79
  61. package/src/commands/portal/update.ts +0 -93
  62. package/src/commands/whatsapp/broadcast.ts +0 -110
  63. package/src/commands/whatsapp/channels.ts +0 -109
  64. package/src/commands/whatsapp/chat.ts +0 -85
  65. package/src/commands/whatsapp/connect.ts +0 -220
  66. package/src/commands/whatsapp/delete-webhook.ts +0 -43
  67. package/src/commands/whatsapp/index.ts +0 -37
  68. package/src/commands/whatsapp/register-webhook.ts +0 -47
  69. package/src/commands/whatsapp/send-template.ts +0 -94
  70. package/src/commands/whatsapp/send.ts +0 -55
  71. package/src/commands/whatsapp/widget.ts +0 -75
  72. package/src/commands/workers/deploy.ts +0 -156
  73. package/src/commands/workers/index.ts +0 -30
  74. package/src/commands/workers/list.ts +0 -105
  75. package/src/commands/workers/logs.ts +0 -99
  76. package/src/commands/workers/remove.ts +0 -103
  77. package/src/commands/workers/secret.ts +0 -191
  78. package/src/commands/workers/test.ts +0 -145
  79. package/src/schemas/agent.config.schema.json +0 -569
  80. package/src/studio/api/sseClient.ts +0 -91
  81. package/src/studio/api/studioApi.ts +0 -27
  82. package/src/studio/api/types.ts +0 -96
  83. package/src/studio/components/App.tsx +0 -274
  84. package/src/studio/components/ChatLog.tsx +0 -121
  85. package/src/studio/components/ConnectCard.tsx +0 -171
  86. package/src/studio/components/Footer.tsx +0 -38
  87. package/src/studio/components/Header.tsx +0 -39
  88. package/src/studio/components/Input.tsx +0 -176
  89. package/src/studio/components/Message.tsx +0 -87
  90. package/src/studio/components/Suggestions.tsx +0 -26
  91. package/src/studio/components/ToolCall.tsx +0 -58
  92. package/src/studio/connect/api.ts +0 -98
  93. package/src/studio/connect/polling.ts +0 -94
  94. package/src/studio/connect/types.ts +0 -141
  95. package/src/studio/index.ts +0 -58
  96. package/src/studio/render/markdown.ts +0 -93
  97. package/src/studio/render/steps.ts +0 -57
  98. package/src/studio/runOneShot.ts +0 -114
  99. package/src/studio/runRepl.tsx +0 -82
  100. package/src/studio/slash/handlers.ts +0 -226
  101. package/src/studio/slash/parser.ts +0 -41
  102. package/src/studio/slash/registry.ts +0 -54
  103. package/src/studio/state/store.ts +0 -279
  104. package/src/types/agent.ts +0 -63
  105. package/src/types/auth.ts +0 -9
  106. package/src/types/common.ts +0 -10
  107. package/src/types/message.ts +0 -34
  108. package/src/types/portal.ts +0 -56
  109. package/src/types/workers.ts +0 -86
  110. package/src/utils/agent-errors.ts +0 -67
  111. package/src/utils/api.ts +0 -35
  112. package/src/utils/banner.ts +0 -99
  113. package/src/utils/credentials.ts +0 -38
  114. package/src/utils/help.ts +0 -51
  115. package/src/utils/logger.ts +0 -40
  116. package/src/utils/ui.ts +0 -109
  117. package/tsconfig.json +0 -18
@@ -1,93 +0,0 @@
1
- import { Command } from 'commander';
2
- import { Portal } from 'plazbot';
3
- import { getStoredCredentials } from '../../utils/credentials.js';
4
- import { logger } from '../../utils/logger.js';
5
- import { addExamples } from '../../utils/help.js';
6
- import { PortalCommandOptions } from '../../types/portal.js';
7
-
8
- export const updateCommand = new Command('update')
9
- .description('Update an existing portal')
10
- .argument('<portalId>', 'ID of the portal to update')
11
- .option('-n, --name <name>', 'New portal name')
12
- .option('-t, --title <title>', 'New portal title')
13
- .option('-s, --subtitle <subtitle>', 'New portal subtitle')
14
- .option('-l, --logo <url>', 'New logo URL')
15
- .option('-d, --logo-dark <url>', 'New dark-mode logo URL')
16
- .option('-a, --access <type>', 'Access type (direct or form)')
17
- .option('--disabled <value>', 'Disabled state (true or false)')
18
- .option('--brand-off <value>', 'Brand-off state (true or false)')
19
- .option('--dev', 'Use development environment', false)
20
- .action(async (portalId: string, options: PortalCommandOptions) => {
21
- try {
22
- // Obtener credenciales guardadas
23
- const credentials = await getStoredCredentials();
24
-
25
- const portal = new Portal({
26
- workspaceId: credentials.workspace,
27
- apiKey: credentials.apiKey,
28
- zone: credentials.zone,
29
- ...(options.dev && { customUrl: "http://localhost:5090" })
30
- });
31
-
32
- // Obtener detalles actuales del portal
33
- const currentPortal = await portal.getPortal(portalId);
34
-
35
- logger.title('Current portal');
36
- logger.json(currentPortal.portal);
37
-
38
- // Construir objeto de actualización solo con los campos proporcionados
39
- const updateData: any = { id: portalId };
40
-
41
- if (options.name) updateData.name = options.name;
42
- if (options.title) updateData.title = options.title;
43
- if (options.subtitle) updateData.subtitle = options.subtitle;
44
- if (options.logo) updateData.logo = options.logo;
45
- if (options.logoDark) updateData.logodark = options.logoDark;
46
- if (options.access) {
47
- if (options.access !== 'direct' && options.access !== 'form') {
48
- throw new Error('Access type must be "direct" or "form"');
49
- }
50
- updateData.access = options.access;
51
- }
52
- if (options.disabled) {
53
- if (options.disabled !== 'true' && options.disabled !== 'false') {
54
- throw new Error('The disabled value must be "true" or "false"');
55
- }
56
- updateData.disabled = options.disabled === 'true';
57
- }
58
- if (options.brandOff) {
59
- if (options.brandOff !== 'true' && options.brandOff !== 'false') {
60
- throw new Error('The brand-off value must be "true" or "false"');
61
- }
62
- updateData.brandOff = options.brandOff === 'true';
63
- }
64
-
65
- logger.title('Changes to apply');
66
- logger.json(updateData);
67
-
68
- const result = await portal.updatePortal(updateData);
69
-
70
- logger.success('Portal updated successfully');
71
- logger.label('Message', result.message);
72
-
73
- if (options.dev) {
74
- logger.warning('\nEnvironment: development');
75
- }
76
-
77
- } catch (error) {
78
- const message = error instanceof Error ? error.message : 'Unknown error while updating the portal';
79
- logger.error(message);
80
- process.exit(1);
81
- }
82
- });
83
-
84
- addExamples(updateCommand, [
85
- { description: 'Rename a portal',
86
- command: 'plazbot portal update prt_AbcDef123 -n "Support Center"' },
87
- { description: 'Switch access to form mode and update title',
88
- command: 'plazbot portal update prt_AbcDef123 -a form -t "Sign in to continue"' },
89
- { description: 'Disable a portal',
90
- command: 'plazbot portal update prt_AbcDef123 --disabled true' },
91
- { description: 'Hide the Plazbot brand and update the logo',
92
- command: 'plazbot portal update prt_AbcDef123 --brand-off true -l https://files.example.com/logo.png' },
93
- ]);
@@ -1,110 +0,0 @@
1
- import { Command } from 'commander';
2
- import { Message } from 'plazbot';
3
- import { getStoredCredentials } from '../../utils/credentials.js';
4
- import { logger } from '../../utils/logger.js';
5
- import { addExamples } from '../../utils/help.js';
6
- import { createSpinner, theme, section, progressBar } from '../../utils/ui.js';
7
- import fs from 'fs/promises';
8
-
9
- export const broadcastCommand = new Command('broadcast')
10
- .description('Bulk send WhatsApp template messages')
11
- .requiredOption('-t, --template <name>', 'Template name')
12
- .requiredOption('--phones <file>', 'CSV file with phone numbers (one per line)')
13
- .option('--var <vars...>', 'Body variables (format: name=value)')
14
- .option('--delay <ms>', 'Delay between messages in ms', '1000')
15
- .option('--dev', 'Use development environment', false)
16
- .action(async (options: any) => {
17
- try {
18
- const credentials = await getStoredCredentials();
19
-
20
- const messageClient = new Message({
21
- workspaceId: credentials.workspace,
22
- apiKey: credentials.apiKey,
23
- zone: credentials.zone,
24
- ...(options.dev && { customUrl: "http://localhost:5090" })
25
- });
26
-
27
- // Leer archivo de telefonos
28
- let phones: string[];
29
- try {
30
- const content = await fs.readFile(options.phones, 'utf-8');
31
- phones = content.split('\n')
32
- .map(line => line.trim())
33
- .filter(line => line && !line.startsWith('#'));
34
- } catch {
35
- throw new Error(`Could not read file: ${options.phones}`);
36
- }
37
-
38
- if (phones.length === 0) {
39
- throw new Error('The file does not contain phone numbers');
40
- }
41
-
42
- // Parsear variables
43
- const variablesBody = parseVariables(options.var);
44
-
45
- console.log(section('WhatsApp Broadcast'));
46
- logger.label('Template', options.template);
47
- logger.label('Recipients', String(phones.length));
48
- logger.label('Delay', `${options.delay}ms`);
49
- if (variablesBody.length > 0) {
50
- logger.label('Variables', variablesBody.map(v => `${v.variable}=${v.value}`).join(', '));
51
- }
52
- console.log();
53
-
54
- const delay = parseInt(options.delay) || 1000;
55
- let sent = 0;
56
- let failed = 0;
57
-
58
- for (let i = 0; i < phones.length; i++) {
59
- const phone = phones[i];
60
- try {
61
- const params: any = {
62
- to: phone,
63
- template: options.template,
64
- };
65
- if (variablesBody.length > 0) params.variablesBody = variablesBody;
66
-
67
- await messageClient.onConversation(params);
68
- sent++;
69
- } catch {
70
- failed++;
71
- console.log(theme.error(` ✖ Failed: ${phone}`));
72
- }
73
-
74
- // Progress
75
- console.log(` ${progressBar(i + 1, phones.length)}`);
76
-
77
- // Rate limiting
78
- if (i < phones.length - 1) {
79
- await new Promise(resolve => setTimeout(resolve, delay));
80
- }
81
- }
82
-
83
- console.log(section('Result'));
84
- logger.label('Sent', theme.success(String(sent)));
85
- logger.label('Failed', failed > 0 ? theme.error(String(failed)) : '0');
86
- logger.label('Total', String(phones.length));
87
-
88
- } catch (error: unknown) {
89
- const message = (error as Error)?.message || 'Unknown error';
90
- logger.error(message);
91
- process.exit(1);
92
- }
93
- });
94
-
95
- addExamples(broadcastCommand, [
96
- { description: 'Broadcast a template to a phone list (default 1s delay)',
97
- command: 'plazbot whatsapp broadcast -t welcome --phones ./phones.csv' },
98
- { description: 'Broadcast with body variables',
99
- command: 'plazbot whatsapp broadcast -t promo --phones ./contacts.csv --var amount=20 code=PROMO20' },
100
- { description: 'Slow broadcast (3s delay) to avoid rate limits',
101
- command: 'plazbot whatsapp broadcast -t reminder --phones ./list.csv --delay 3000' },
102
- ]);
103
-
104
- function parseVariables(vars: string[] | undefined): { variable: string; value: string }[] {
105
- if (!vars) return [];
106
- return vars.map(v => {
107
- const [variable, ...rest] = v.split('=');
108
- return { variable: variable.trim(), value: rest.join('=').trim() };
109
- }).filter(v => v.variable && v.value);
110
- }
@@ -1,109 +0,0 @@
1
- import { Command } from 'commander';
2
- import { Agent } from 'plazbot';
3
- import { getStoredCredentials } from '../../utils/credentials.js';
4
- import { logger } from '../../utils/logger.js';
5
- import { addExamples } from '../../utils/help.js';
6
- import { createSpinner, createTable, theme, section, statusBadge } from '../../utils/ui.js';
7
-
8
- export const channelsCommand = new Command('channels')
9
- .description('List agents with assigned WhatsApp channels')
10
- .option('--dev', 'Use development environment', false)
11
- .action(async (options: any) => {
12
- try {
13
- const credentials = await getStoredCredentials();
14
- const agent = new Agent({
15
- workspaceId: credentials.workspace,
16
- apiKey: credentials.apiKey,
17
- zone: credentials.zone,
18
- ...(options.dev && { customUrl: "http://localhost:5090" })
19
- });
20
-
21
- const spinner = createSpinner('Loading channels...');
22
- spinner.start();
23
- const agents = await agent.getAgents();
24
- spinner.stop();
25
-
26
- console.log(section('WhatsApp Channels'));
27
-
28
- const whatsappAgents = (agents || []).filter((a: any) =>
29
- a.channels && a.channels.some((c: any) => c.channel === 'whatsapp')
30
- );
31
-
32
- if (whatsappAgents.length === 0) {
33
- console.log(theme.muted('\n No agents with WhatsApp configured'));
34
- console.log(theme.muted(' Use the wizard: plazbot agent create\n'));
35
- return;
36
- }
37
-
38
- const rows = whatsappAgents.map((a: any) => {
39
- const waChannel = a.channels.find((c: any) => c.channel === 'whatsapp');
40
- return [
41
- a.id || a._id,
42
- a.name,
43
- waChannel?.key || 'N/A',
44
- statusBadge(a.enable !== false),
45
- ];
46
- });
47
-
48
- console.log(createTable(['Agent ID', 'Name', 'WhatsApp Number', 'Status'], rows));
49
-
50
- } catch (error) {
51
- const message = error instanceof Error ? error.message : 'Unknown error';
52
- logger.error(message);
53
- process.exit(1);
54
- }
55
- });
56
-
57
- addExamples(channelsCommand, [
58
- { description: 'List agents that have a WhatsApp number assigned',
59
- command: 'plazbot whatsapp channels' },
60
- ]);
61
-
62
- export const assignCommand = new Command('assign')
63
- .description('Assign an agent to a WhatsApp number')
64
- .argument('<phone>', 'WhatsApp number')
65
- .argument('<agentId>', 'Agent ID')
66
- .option('--dev', 'Use development environment', false)
67
- .action(async (phone: string, agentId: string, options: any) => {
68
- try {
69
- const credentials = await getStoredCredentials();
70
- const agent = new Agent({
71
- workspaceId: credentials.workspace,
72
- apiKey: credentials.apiKey,
73
- zone: credentials.zone,
74
- ...(options.dev && { customUrl: "http://localhost:5090" })
75
- });
76
-
77
- const spinner = createSpinner('Loading agent...');
78
- spinner.start();
79
- const agentData = await agent.getAgentById({ id: agentId });
80
- spinner.stop();
81
-
82
- const channels = agentData.channels || [];
83
-
84
- // Verificar si ya tiene este canal
85
- const existing = channels.findIndex((c: any) => c.channel === 'whatsapp' && c.key === phone);
86
- if (existing >= 0) {
87
- logger.warning('This number is already assigned to this agent');
88
- return;
89
- }
90
-
91
- // Agregar canal
92
- channels.push({ channel: 'whatsapp', key: phone, multianswer: false });
93
-
94
- const updateSpinner = createSpinner('Assigning...');
95
- updateSpinner.start();
96
- await agent.updateAgent(agentId, { channels });
97
- updateSpinner.succeed(`Agent "${agentData.name}" assigned to WhatsApp ${phone}`);
98
-
99
- } catch (error) {
100
- const message = error instanceof Error ? error.message : 'Unknown error';
101
- logger.error(message);
102
- process.exit(1);
103
- }
104
- });
105
-
106
- addExamples(assignCommand, [
107
- { description: 'Assign a WhatsApp number to an agent',
108
- command: 'plazbot whatsapp assign 51912345678 agt_AbcDef123' },
109
- ]);
@@ -1,85 +0,0 @@
1
- import { Command } from 'commander';
2
- import { Message } from 'plazbot';
3
- import { getStoredCredentials } from '../../utils/credentials.js';
4
- import { logger } from '../../utils/logger.js';
5
- import { addExamples } from '../../utils/help.js';
6
- import { createSpinner, theme } from '../../utils/ui.js';
7
- import readline from 'readline';
8
- import chalk from 'chalk';
9
-
10
- export const whatsappChatCommand = new Command('chat')
11
- .description('Interactive direct chat over WhatsApp')
12
- .argument('<phone>', 'Destination phone number (with country code)')
13
- .option('--dev', 'Use development environment', false)
14
- .action(async (phone: string, options: any) => {
15
- try {
16
- const credentials = await getStoredCredentials();
17
-
18
- const messageClient = new Message({
19
- workspaceId: credentials.workspace,
20
- apiKey: credentials.apiKey,
21
- zone: credentials.zone,
22
- ...(options.dev && { customUrl: "http://localhost:5090" })
23
- });
24
-
25
- const rl = readline.createInterface({
26
- input: process.stdin,
27
- output: process.stdout,
28
- });
29
-
30
- console.clear();
31
- console.log();
32
- console.log(chalk.hex('#25D366')(' ┌' + '─'.repeat(50) + '┐'));
33
- console.log(chalk.hex('#25D366')(' │') + chalk.bold(` WhatsApp Chat`).padEnd(60) + chalk.hex('#25D366')('│'));
34
- console.log(chalk.hex('#25D366')(' │') + chalk.gray(` Destination: ${phone}`).padEnd(60) + chalk.hex('#25D366')('│'));
35
- console.log(chalk.hex('#25D366')(' │') + chalk.gray(' /exit to quit').padEnd(60) + chalk.hex('#25D366')('│'));
36
- console.log(chalk.hex('#25D366')(' └' + '─'.repeat(50) + '┘'));
37
- console.log();
38
-
39
- const ask = () => {
40
- rl.question(chalk.hex('#25D366')(' > '), async (message) => {
41
- if (!message.trim()) {
42
- ask();
43
- return;
44
- }
45
-
46
- if (message.toLowerCase() === '/exit') {
47
- console.log(chalk.gray('\n Chat ended.\n'));
48
- rl.close();
49
- return;
50
- }
51
-
52
- try {
53
- const spinner = createSpinner('Sending...');
54
- spinner.start();
55
-
56
- await messageClient.onWhatsappMessage({
57
- message,
58
- to: phone,
59
- });
60
-
61
- spinner.succeed('Sent');
62
- ask();
63
- } catch (error) {
64
- const msg = error instanceof Error ? error.message : 'Error';
65
- console.log(chalk.hex('#EF5350')(` ✖ ${msg}`));
66
- ask();
67
- }
68
- });
69
- };
70
-
71
- ask();
72
-
73
- } catch (error) {
74
- const message = error instanceof Error ? error.message : 'Unknown error';
75
- logger.error(message);
76
- process.exit(1);
77
- }
78
- });
79
-
80
- addExamples(whatsappChatCommand, [
81
- { description: 'Open an interactive chat with a WhatsApp number',
82
- command: 'plazbot whatsapp chat 51912345678' },
83
- { description: 'Chat through the development backend',
84
- command: 'plazbot whatsapp chat 51912345678 --dev' },
85
- ]);
@@ -1,220 +0,0 @@
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.js';
6
- import { logger } from '../../utils/logger.js';
7
- import { addExamples } from '../../utils/help.js';
8
- import { theme, section } from '../../utils/ui.js';
9
-
10
- function getBaseUrl(zone: string, dev: boolean): string {
11
- if (dev) return 'http://localhost:5090';
12
- return zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com';
13
- }
14
-
15
- function getIntegrationLabel(ig: any): string {
16
- if (ig.type === 'whatsapp') {
17
- return ig.aliasName
18
- ? `${ig.aliasName} (${ig.cellphoneNumberFormat || ig.cellphoneNumber || ''})`
19
- : ig.cellphoneNumberFormat || ig.cellphoneNumber || ig.id;
20
- }
21
- return ig.aliasName || ig.srcName || ig.id;
22
- }
23
-
24
- async function fetchWorkspaceIntegrations(baseUrl: string, workspaceId: string, headers: any): Promise<any[]> {
25
- try {
26
- const wkRes = await axios.get(`${baseUrl}/api/workspace/${workspaceId}`, { headers });
27
- const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
28
- return wsData?.integrations || [];
29
- } catch {
30
- return [];
31
- }
32
- }
33
-
34
- async function activateIntegration(baseUrl: string, workspaceId: string, integrationId: string, headers: any): Promise<boolean> {
35
- try {
36
- const res = await axios.post(
37
- `${baseUrl}/api/workspace/${workspaceId}/integrations/${integrationId}/activate`,
38
- { isActive: true },
39
- { headers }
40
- );
41
- return res.data?.success === true;
42
- } catch {
43
- return false;
44
- }
45
- }
46
-
47
- export const whatsappConnectCommand = new Command('connect')
48
- .description('Connect a WhatsApp number to the workspace (without creating an agent)')
49
- .option('--dev', 'Use development environment', false)
50
- .option('--business', 'Connect as WhatsApp Business', false)
51
- .action(async (options: any) => {
52
- try {
53
- const credentials = await getStoredCredentials();
54
- const baseUrl = getBaseUrl(credentials.zone, options.dev);
55
- const headers = {
56
- 'Authorization': `Bearer ${credentials.apiKey}`,
57
- 'x-workspace-id': credentials.workspace,
58
- 'Content-Type': 'application/json',
59
- };
60
-
61
- const channelType = options.business ? 'whatsapp_business' : 'whatsapp';
62
- const channelLabel = options.business ? 'WhatsApp Business' : 'WhatsApp';
63
-
64
- console.log(section(`Connect ${channelLabel}`));
65
- console.log();
66
-
67
- // Cargar integraciones existentes
68
- const loadSpinner = ora({ text: chalk.gray('Loading existing integrations...'), spinner: 'dots', color: 'green' }).start();
69
- const allIntegrations = await fetchWorkspaceIntegrations(baseUrl, credentials.workspace, headers);
70
- const existing = allIntegrations.filter((ig: any) => ig.type === 'whatsapp');
71
- loadSpinner.stop();
72
-
73
- if (existing.length > 0) {
74
- console.log(theme.muted(` ${existing.length} number(s) already connected:`));
75
- existing.forEach((ig: any) => {
76
- const label = getIntegrationLabel(ig);
77
- const status = ig.isActive ? chalk.hex('#66BB6A')('active') : chalk.hex('#EF5350')('inactive');
78
- console.log(` ${theme.muted('•')} ${label} ${status}`);
79
- });
80
- console.log();
81
- }
82
-
83
- // Obtener o generar link de onboarding (mismo flujo que Plazbot Front)
84
- const ONBOARDING_SHORT_DOMAIN = 'https://co.plzb.link';
85
- const spinner = ora({ text: chalk.gray('Fetching connection link...'), spinner: 'dots', color: 'green' }).start();
86
-
87
- let linkData: any = null;
88
-
89
- // 1. Intentar obtener link existente (GET)
90
- try {
91
- const getRes = await axios.get(
92
- `${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link`,
93
- { headers, params: { type: channelType } }
94
- );
95
- linkData = getRes.data?.data ?? null;
96
- } catch {
97
- linkData = null;
98
- }
99
-
100
- // 2. Si existe pero esta expirado, regenerar
101
- if (linkData?.expiresAt && new Date(linkData.expiresAt) < new Date()) {
102
- try {
103
- const regenRes = await axios.post(
104
- `${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link/regenerate`,
105
- { type: channelType },
106
- { headers }
107
- );
108
- linkData = regenRes.data?.data ?? null;
109
- } catch {
110
- linkData = null;
111
- }
112
- }
113
-
114
- // 3. Si no existe, generar uno nuevo (POST)
115
- if (!linkData?.token) {
116
- try {
117
- const createRes = await axios.post(
118
- `${baseUrl}/api/workspace/${credentials.workspace}/onboarding-link`,
119
- { type: channelType },
120
- { headers }
121
- );
122
- linkData = createRes.data?.data ?? null;
123
- } catch {
124
- linkData = null;
125
- }
126
- }
127
-
128
- if (!linkData?.token) {
129
- spinner.fail(chalk.hex('#EF5350')('Could not obtain the connection link'));
130
- process.exit(1);
131
- }
132
-
133
- const shortUrl = `${ONBOARDING_SHORT_DOMAIN}/${linkData.token}`;
134
- const expiresAt = linkData.expiresAt ? new Date(linkData.expiresAt).toLocaleDateString('en-US', { day: 'numeric', month: 'long', year: 'numeric' }) : '';
135
- spinner.stop();
136
-
137
- // Mostrar link
138
- const W = 54;
139
- const pad = (text: string) => text + ' '.repeat(Math.max(0, W - text.length));
140
- const border = chalk.hex('#25D366');
141
-
142
- console.log(border(' ┌' + '─'.repeat(W) + '┐'));
143
- console.log(border(' │') + chalk.bold(pad(' Open this link in your browser:')) + border('│'));
144
- console.log(border(' │') + ' '.repeat(W) + border('│'));
145
- console.log(border(' │') + chalk.hex('#22d3ee')(pad(` ${shortUrl}`)) + border('│'));
146
- console.log(border(' │') + ' '.repeat(W) + border('│'));
147
- console.log(border(' │') + chalk.gray(pad(' Scan the QR code from your WhatsApp')) + border('│'));
148
- if (expiresAt) {
149
- console.log(border(' │') + chalk.gray(pad(` Expires: ${expiresAt}`)) + border('│'));
150
- }
151
- console.log(border(' └' + '─'.repeat(W) + '┘'));
152
- console.log();
153
-
154
- // Guardar IDs actuales para detectar nuevos
155
- const prevIds = new Set(allIntegrations.map((i: any) => i.id));
156
-
157
- // Polling para detectar nueva integracion
158
- const pollSpinner = ora({ text: chalk.gray('Waiting for connection... (Ctrl+C to cancel)'), spinner: 'dots', color: 'cyan' }).start();
159
- const POLL_INTERVAL = 5000;
160
- const MAX_POLLS = 60; // 5 minutos
161
- let detected: any = null;
162
-
163
- for (let i = 0; i < MAX_POLLS; i++) {
164
- await new Promise(r => setTimeout(r, POLL_INTERVAL));
165
-
166
- try {
167
- const freshIntegrations = await fetchWorkspaceIntegrations(baseUrl, credentials.workspace, headers);
168
- const newOnes = freshIntegrations.filter((ig: any) => !prevIds.has(ig.id));
169
- if (newOnes.length > 0) {
170
- const match = newOnes.find((ig: any) => ig.type === 'whatsapp');
171
- detected = match || newOnes[0];
172
- break;
173
- }
174
- } catch {}
175
-
176
- const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
177
- pollSpinner.text = chalk.gray(`Waiting for connection... ${elapsed}s (Ctrl+C to cancel)`);
178
- }
179
-
180
- if (detected) {
181
- const detectedName = getIntegrationLabel(detected);
182
- pollSpinner.succeed(chalk.hex('#4ade80')(`Connection detected: ${detectedName}`));
183
-
184
- // Activar la integracion
185
- const actSpinner = ora({ text: chalk.gray('Activating integration...'), spinner: 'dots', color: 'green' }).start();
186
- const activated = await activateIntegration(baseUrl, credentials.workspace, detected.id, headers);
187
- if (activated) {
188
- actSpinner.succeed(chalk.hex('#4ade80')('Integration activated successfully'));
189
- } else {
190
- actSpinner.warn(chalk.hex('#FFA726')('Could not activate automatically. Activate it from the platform.'));
191
- }
192
-
193
- console.log();
194
- console.log(theme.success(' WhatsApp connected successfully'));
195
- console.log(theme.muted(` Number: ${detectedName}`));
196
- console.log();
197
- console.log(theme.muted(' You can now:'));
198
- console.log(theme.muted(' plazbot whatsapp send-message Send a message'));
199
- console.log(theme.muted(' plazbot whatsapp chat <phone> Interactive chat'));
200
- console.log(theme.muted(' plazbot whatsapp assign <phone> <agentId> Assign to an agent'));
201
- console.log();
202
- } else {
203
- pollSpinner.warn(chalk.hex('#FFA726')('Timeout: no connection detected within 5 minutes.'));
204
- console.log(theme.muted(' You can try again with: plazbot whatsapp connect'));
205
- console.log();
206
- }
207
-
208
- } catch (error) {
209
- const message = error instanceof Error ? error.message : 'Unknown error';
210
- logger.error(message);
211
- process.exit(1);
212
- }
213
- });
214
-
215
- addExamples(whatsappConnectCommand, [
216
- { description: 'Generate a QR connection link for WhatsApp',
217
- command: 'plazbot whatsapp connect' },
218
- { description: 'Connect a WhatsApp Business number',
219
- command: 'plazbot whatsapp connect --business' },
220
- ]);
@@ -1,43 +0,0 @@
1
- import { Command } from 'commander';
2
- import { Message } from 'plazbot';
3
- import { getStoredCredentials } from '../../utils/credentials.js';
4
- import { logger } from '../../utils/logger.js';
5
- import { addExamples } from '../../utils/help.js';
6
-
7
- interface DeleteWebhookOptions {
8
- dev?: boolean;
9
- number: string;
10
- }
11
-
12
- export const deleteWebhookCommand = new Command('delete-webhook')
13
- .description('Delete the WhatsApp webhook')
14
- .requiredOption('-n, --number <phone>', 'WhatsApp number (with country code, e.g.: 51912345678)')
15
- .option('--dev', 'Use development environment', false)
16
- .action(async (options: DeleteWebhookOptions) => {
17
- try {
18
- const credentials = await getStoredCredentials();
19
- const messageClient = new Message({
20
- workspaceId: credentials.workspace,
21
- apiKey: credentials.apiKey,
22
- zone: credentials.zone,
23
- ...(options.dev && { customUrl: "http://localhost:5090" })
24
- });
25
-
26
- logger.title('Deleting WhatsApp webhook');
27
- logger.label('Number', options.number);
28
-
29
- await messageClient.deleteWebhook({
30
- number: options.number
31
- });
32
-
33
- logger.success('Webhook deleted successfully');
34
- } catch (error: unknown) {
35
- logger.error((error as Error).message);
36
- process.exit(1);
37
- }
38
- });
39
-
40
- addExamples(deleteWebhookCommand, [
41
- { description: 'Remove the webhook from a WhatsApp number',
42
- command: 'plazbot whatsapp delete-webhook -n 51912345678' },
43
- ]);