plazbot-cli 0.3.7 → 0.4.1
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/README.md +38 -2
- package/dist/commands/auth/browser-login.js +121 -0
- package/dist/commands/auth/index.js +5 -1
- package/dist/commands/auth/login.js +1 -3
- package/dist/commands/auth/logout.js +4 -3
- package/dist/commands/auth/status.js +8 -5
- package/dist/commands/auth/workspace.js +73 -0
- package/dist/commands/whatsapp/connect-flow.js +137 -0
- package/dist/commands/whatsapp/connect.js +26 -160
- package/dist/commands/whatsapp/index.js +4 -0
- package/dist/commands/whatsapp/setup.js +242 -0
- package/dist/schemas/agent.config.schema.json +24 -0
- package/dist/studio/components/App.js +1 -1
- package/dist/studio/runOneShot.js +2 -2
- package/dist/studio/runRepl.js +1 -1
- package/dist/utils/agent-errors.js +2 -2
- package/dist/utils/banner.js +10 -9
- package/dist/utils/credentials.js +31 -3
- package/dist/utils/open-browser.js +17 -0
- package/package.json +14 -6
- package/.claude/settings.local.json +0 -19
- package/CLAUDE.md +0 -121
- package/agent.config.json +0 -21
- package/dist/studio/components/AgentPanel.js +0 -35
- package/dist/studio/render/json.js +0 -16
- package/dist/studio/whatsapp/api.js +0 -63
- package/dist/studio/whatsapp/polling.js +0 -77
- package/dist/studio/whatsapp/types.js +0 -31
- package/src/cli.ts +0 -57
- package/src/commands/agent/ai-config.ts +0 -164
- package/src/commands/agent/chat.ts +0 -225
- package/src/commands/agent/copy.ts +0 -46
- package/src/commands/agent/create.ts +0 -103
- package/src/commands/agent/delete.ts +0 -71
- package/src/commands/agent/enable-widget.ts +0 -67
- package/src/commands/agent/export.ts +0 -430
- package/src/commands/agent/files.ts +0 -171
- package/src/commands/agent/get.ts +0 -233
- package/src/commands/agent/index.ts +0 -55
- package/src/commands/agent/list.ts +0 -58
- package/src/commands/agent/monitor.ts +0 -375
- package/src/commands/agent/on-message.ts +0 -82
- package/src/commands/agent/set.ts +0 -149
- package/src/commands/agent/templates.ts +0 -892
- package/src/commands/agent/tools.ts +0 -168
- package/src/commands/agent/update.ts +0 -61
- package/src/commands/agent/validate.ts +0 -135
- package/src/commands/agent/wizard.ts +0 -756
- package/src/commands/auth/index.ts +0 -10
- package/src/commands/auth/login.ts +0 -72
- package/src/commands/auth/logout.ts +0 -28
- package/src/commands/auth/status.ts +0 -28
- package/src/commands/portal/add-agent.ts +0 -66
- package/src/commands/portal/add-link.ts +0 -47
- package/src/commands/portal/clear-links.ts +0 -33
- package/src/commands/portal/create.ts +0 -58
- package/src/commands/portal/delete.ts +0 -70
- package/src/commands/portal/get.ts +0 -77
- package/src/commands/portal/index.ts +0 -32
- package/src/commands/portal/list.ts +0 -79
- package/src/commands/portal/update.ts +0 -93
- package/src/commands/whatsapp/broadcast.ts +0 -110
- package/src/commands/whatsapp/channels.ts +0 -109
- package/src/commands/whatsapp/chat.ts +0 -85
- package/src/commands/whatsapp/connect.ts +0 -220
- package/src/commands/whatsapp/delete-webhook.ts +0 -43
- package/src/commands/whatsapp/index.ts +0 -37
- package/src/commands/whatsapp/register-webhook.ts +0 -47
- package/src/commands/whatsapp/send-template.ts +0 -94
- package/src/commands/whatsapp/send.ts +0 -55
- package/src/commands/whatsapp/widget.ts +0 -75
- package/src/commands/workers/deploy.ts +0 -156
- package/src/commands/workers/index.ts +0 -30
- package/src/commands/workers/list.ts +0 -105
- package/src/commands/workers/logs.ts +0 -99
- package/src/commands/workers/remove.ts +0 -103
- package/src/commands/workers/secret.ts +0 -191
- package/src/commands/workers/test.ts +0 -145
- package/src/schemas/agent.config.schema.json +0 -569
- package/src/studio/api/sseClient.ts +0 -91
- package/src/studio/api/studioApi.ts +0 -27
- package/src/studio/api/types.ts +0 -96
- package/src/studio/components/App.tsx +0 -274
- package/src/studio/components/ChatLog.tsx +0 -121
- package/src/studio/components/ConnectCard.tsx +0 -171
- package/src/studio/components/Footer.tsx +0 -38
- package/src/studio/components/Header.tsx +0 -39
- package/src/studio/components/Input.tsx +0 -176
- package/src/studio/components/Message.tsx +0 -87
- package/src/studio/components/Suggestions.tsx +0 -26
- package/src/studio/components/ToolCall.tsx +0 -58
- package/src/studio/connect/api.ts +0 -98
- package/src/studio/connect/polling.ts +0 -94
- package/src/studio/connect/types.ts +0 -141
- package/src/studio/index.ts +0 -58
- package/src/studio/render/markdown.ts +0 -93
- package/src/studio/render/steps.ts +0 -57
- package/src/studio/runOneShot.ts +0 -114
- package/src/studio/runRepl.tsx +0 -82
- package/src/studio/slash/handlers.ts +0 -226
- package/src/studio/slash/parser.ts +0 -41
- package/src/studio/slash/registry.ts +0 -54
- package/src/studio/state/store.ts +0 -279
- package/src/types/agent.ts +0 -63
- package/src/types/auth.ts +0 -9
- package/src/types/common.ts +0 -10
- package/src/types/message.ts +0 -34
- package/src/types/portal.ts +0 -56
- package/src/types/workers.ts +0 -86
- package/src/utils/agent-errors.ts +0 -67
- package/src/utils/api.ts +0 -35
- package/src/utils/banner.ts +0 -99
- package/src/utils/credentials.ts +0 -38
- package/src/utils/help.ts +0 -51
- package/src/utils/logger.ts +0 -40
- package/src/utils/ui.ts +0 -109
- package/tsconfig.json +0 -18
package/README.md
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
# Plazbot CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI oficial de Plazbot: conecta WhatsApp, envía mensajes, gestiona agentes IA y despliega workers desde tu terminal.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Empieza en 3 comandos
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g plazbot-cli
|
|
9
|
+
plazbot login # abre el navegador y autorizas (sin copiar API key)
|
|
10
|
+
plazbot whatsapp setup # conecta tu número y prueba tu primer mensaje
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`plazbot whatsapp setup` te guía paso a paso: conecta un número (WhatsApp API o tu
|
|
14
|
+
WhatsApp Business actual), te pide escribir "hola" desde tu celular, responde por la API
|
|
15
|
+
y registra tu webhook. Al final te da el código para tu app.
|
|
16
|
+
|
|
17
|
+
### Sesión
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
plazbot login [--zone LA|EU] [--no-browser] # login por navegador
|
|
21
|
+
plazbot whoami # sesión activa
|
|
22
|
+
plazbot workspace list | use <id> # cambiar de workspace
|
|
23
|
+
plazbot logout
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
En CI o servidores usa variables de entorno en lugar de `plazbot login`:
|
|
27
|
+
`PLAZBOT_API_KEY`, `PLAZBOT_WORKSPACE_ID` y `PLAZBOT_ZONE` (`LA` o `EU`).
|
|
28
|
+
|
|
29
|
+
### WhatsApp
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
plazbot whatsapp setup # asistente de primer uso
|
|
33
|
+
plazbot whatsapp connect [--business] # conectar un número
|
|
34
|
+
plazbot whatsapp send-message --to 51912345678 --message "Hola"
|
|
35
|
+
plazbot whatsapp send-template --phone 51912345678 --template welcome --var 1=Ana
|
|
36
|
+
plazbot whatsapp register-webhook --number 51966446311 --url https://tu-app.com/whatsapp
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Documentación: https://developers.plazbot.com/cli
|
|
40
|
+
|
|
41
|
+
## Más funciones
|
|
6
42
|
|
|
7
43
|
🧠 **Agent Studio (REPL interactivo, estilo Claude Code)**
|
|
8
44
|
```bash
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import { apiBaseUrl, appBaseUrl, saveCredentials } from '../../utils/credentials.js';
|
|
7
|
+
import { openBrowser } from '../../utils/open-browser.js';
|
|
8
|
+
import { createSpinner, theme } from '../../utils/ui.js';
|
|
9
|
+
import { logger } from '../../utils/logger.js';
|
|
10
|
+
import { addExamples, addNotes } from '../../utils/help.js';
|
|
11
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
12
|
+
/**
|
|
13
|
+
* Ejecuta el login por navegador y guarda la sesión. Lo usan `plazbot login` y el
|
|
14
|
+
* wizard `plazbot whatsapp setup` cuando no hay sesión. Lanza Error si falla o vence.
|
|
15
|
+
*/
|
|
16
|
+
export async function runBrowserLogin(options) {
|
|
17
|
+
let zone;
|
|
18
|
+
if (options.zone) {
|
|
19
|
+
zone = options.zone.toUpperCase() === 'EU' ? 'EU' : 'LA';
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const answer = await inquirer.prompt([{
|
|
23
|
+
type: 'list',
|
|
24
|
+
name: 'zone',
|
|
25
|
+
message: '¿Dónde creaste tu cuenta de Plazbot?',
|
|
26
|
+
choices: [
|
|
27
|
+
{ name: 'América (appla.plazbot.com)', value: 'LA' },
|
|
28
|
+
{ name: 'Europa (appeu.plazbot.com)', value: 'EU' },
|
|
29
|
+
],
|
|
30
|
+
}]);
|
|
31
|
+
zone = answer.zone;
|
|
32
|
+
}
|
|
33
|
+
const dev = options.dev ?? false;
|
|
34
|
+
const api = apiBaseUrl(zone, dev);
|
|
35
|
+
let data;
|
|
36
|
+
try {
|
|
37
|
+
const start = await axios.post(`${api}/api/cli/auth/start`, { deviceName: os.hostname() });
|
|
38
|
+
data = start.data?.success ? start.data?.data : undefined;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`No se pudo conectar con Plazbot (${axios.isAxiosError(error) ? error.message : 'error de red'}).`);
|
|
42
|
+
}
|
|
43
|
+
if (!data?.deviceCode) {
|
|
44
|
+
throw new Error('No se pudo iniciar el login. Inténtalo de nuevo en unos segundos.');
|
|
45
|
+
}
|
|
46
|
+
const url = `${appBaseUrl(zone, dev)}/cli/authorize?code=${encodeURIComponent(data.userCode)}&r=${encodeURIComponent(data.requestToken)}`;
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(` Tu código de verificación: ${chalk.bold.hex('#22d3ee')(data.userCode)}`);
|
|
49
|
+
console.log();
|
|
50
|
+
const opened = options.browser !== false && openBrowser(url);
|
|
51
|
+
console.log(theme.muted(opened
|
|
52
|
+
? ' Abrimos tu navegador. Si no se abrió, entra a:'
|
|
53
|
+
: ' Abre este link en tu navegador:'));
|
|
54
|
+
console.log(` ${chalk.underline(url)}`);
|
|
55
|
+
console.log();
|
|
56
|
+
const spinner = createSpinner('Esperando la autorización en el navegador... (Ctrl+C para cancelar)');
|
|
57
|
+
spinner.start();
|
|
58
|
+
const deadline = Date.now() + data.expiresIn * 1000;
|
|
59
|
+
const intervalMs = Math.max(2, data.interval || 3) * 1000;
|
|
60
|
+
let approved = null;
|
|
61
|
+
while (Date.now() < deadline) {
|
|
62
|
+
await sleep(intervalMs);
|
|
63
|
+
try {
|
|
64
|
+
const poll = await axios.post(`${api}/api/cli/auth/poll`, { deviceCode: data.deviceCode });
|
|
65
|
+
if (poll.data?.data?.status === 'approved') {
|
|
66
|
+
approved = poll.data.data;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// error de red puntual: seguir intentando hasta el vencimiento
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!approved) {
|
|
75
|
+
spinner.fail('El código venció sin autorización.');
|
|
76
|
+
throw new Error('Ejecuta plazbot login otra vez.');
|
|
77
|
+
}
|
|
78
|
+
const credentials = {
|
|
79
|
+
apiKey: approved.apiKey,
|
|
80
|
+
workspace: approved.workspaceId,
|
|
81
|
+
workspaceName: approved.workspaceName,
|
|
82
|
+
zone: approved.zone || zone,
|
|
83
|
+
userId: approved.userId,
|
|
84
|
+
email: approved.email,
|
|
85
|
+
source: 'login',
|
|
86
|
+
};
|
|
87
|
+
await saveCredentials(credentials);
|
|
88
|
+
spinner.succeed(`Sesión iniciada en ${chalk.bold(approved.workspaceName || approved.workspaceId)} (${credentials.zone})`);
|
|
89
|
+
if (approved.email)
|
|
90
|
+
console.log(theme.muted(` Cuenta: ${approved.email}`));
|
|
91
|
+
console.log();
|
|
92
|
+
return credentials;
|
|
93
|
+
}
|
|
94
|
+
export const browserLoginCommand = new Command('login')
|
|
95
|
+
.description('Inicia sesión desde el navegador (sin copiar API key)')
|
|
96
|
+
.option('-z, --zone <zone>', 'Zona donde creaste tu cuenta: LA (América) o EU (Europa)')
|
|
97
|
+
.option('--no-browser', 'No abrir el navegador automáticamente (solo mostrar el link)')
|
|
98
|
+
.option('--dev', 'Usar el entorno de desarrollo local', false)
|
|
99
|
+
.action(async (options) => {
|
|
100
|
+
try {
|
|
101
|
+
await runBrowserLogin(options);
|
|
102
|
+
console.log(theme.muted(' Siguiente paso:'));
|
|
103
|
+
console.log(` ${chalk.cyan('plazbot whatsapp setup')} ${theme.muted('Conecta WhatsApp y envía tu primer mensaje')}`);
|
|
104
|
+
console.log(` ${chalk.cyan('plazbot whoami')} ${theme.muted('Ver la sesión activa')}`);
|
|
105
|
+
console.log(` ${chalk.cyan('plazbot workspace list')} ${theme.muted('Cambiar de workspace')}`);
|
|
106
|
+
console.log();
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.error(error instanceof Error ? error.message : 'Error desconocido');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
addExamples(browserLoginCommand, [
|
|
114
|
+
{ description: 'Iniciar sesión (pregunta la zona y abre el navegador)', command: 'plazbot login' },
|
|
115
|
+
{ description: 'Cuenta en Europa', command: 'plazbot login --zone EU' },
|
|
116
|
+
{ description: 'Servidor sin navegador: solo muestra el link', command: 'plazbot login --no-browser' },
|
|
117
|
+
]);
|
|
118
|
+
addNotes(browserLoginCommand, [
|
|
119
|
+
'En CI o servidores usa variables de entorno: PLAZBOT_API_KEY, PLAZBOT_WORKSPACE_ID y PLAZBOT_ZONE.',
|
|
120
|
+
'Puedes revocar el acceso en el panel: Configuración → Developer.',
|
|
121
|
+
]);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { loginCommand } from './login.js';
|
|
2
|
+
import { browserLoginCommand } from './browser-login.js';
|
|
2
3
|
import { logoutCommand } from './logout.js';
|
|
3
4
|
import { statusCommand } from './status.js';
|
|
5
|
+
import { workspaceCommand } from './workspace.js';
|
|
4
6
|
export const authCommands = [
|
|
7
|
+
browserLoginCommand,
|
|
5
8
|
loginCommand,
|
|
6
9
|
logoutCommand,
|
|
7
|
-
statusCommand
|
|
10
|
+
statusCommand,
|
|
11
|
+
workspaceCommand
|
|
8
12
|
];
|
|
@@ -3,7 +3,7 @@ import { saveCredentials } from '../../utils/credentials.js';
|
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
4
|
import { addExamples } from '../../utils/help.js';
|
|
5
5
|
export const loginCommand = new Command('init')
|
|
6
|
-
.description('
|
|
6
|
+
.description('Guarda una API key manualmente (para CI; lo normal es plazbot login)')
|
|
7
7
|
.option('-e, --email <email>', 'User email (optional)')
|
|
8
8
|
.requiredOption('-k, --api-key <apiKey>', 'User API key')
|
|
9
9
|
.requiredOption('-w, --workspace <workspace>', 'Workspace ID')
|
|
@@ -12,8 +12,6 @@ export const loginCommand = new Command('init')
|
|
|
12
12
|
.option('--dev', 'Use development environment', false)
|
|
13
13
|
.action(async (options) => {
|
|
14
14
|
try {
|
|
15
|
-
// Limpiar la pantalla
|
|
16
|
-
console.clear();
|
|
17
15
|
// Banner de bienvenida
|
|
18
16
|
logger.info('┌' + '─'.repeat(70) + '┐');
|
|
19
17
|
logger.info('│' + ' Welcome to Plazbot CLI'.padEnd(69) + '│');
|
|
@@ -2,8 +2,9 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { removeCredentials } from '../../utils/credentials.js';
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
4
|
import { addExamples } from '../../utils/help.js';
|
|
5
|
-
export const logoutCommand = new Command('
|
|
6
|
-
.
|
|
5
|
+
export const logoutCommand = new Command('logout')
|
|
6
|
+
.alias('finish')
|
|
7
|
+
.description('Cierra la sesión local (borra ~/.plazbot/config.json)')
|
|
7
8
|
.option('--dev', 'Use development environment', false)
|
|
8
9
|
.action(async (options) => {
|
|
9
10
|
try {
|
|
@@ -21,5 +22,5 @@ export const logoutCommand = new Command('finish')
|
|
|
21
22
|
});
|
|
22
23
|
addExamples(logoutCommand, [
|
|
23
24
|
{ description: 'Remove the local configuration (~/.plazbot/config.json)',
|
|
24
|
-
command: 'plazbot
|
|
25
|
+
command: 'plazbot logout' },
|
|
25
26
|
]);
|
|
@@ -3,25 +3,28 @@ import { getStoredCredentials } from '../../utils/credentials.js';
|
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
4
|
import { addExamples } from '../../utils/help.js';
|
|
5
5
|
import { section } from '../../utils/ui.js';
|
|
6
|
-
export const statusCommand = new Command('
|
|
7
|
-
.
|
|
6
|
+
export const statusCommand = new Command('whoami')
|
|
7
|
+
.alias('status')
|
|
8
|
+
.description('Muestra la sesión activa (cuenta, workspace, zona)')
|
|
8
9
|
.action(async () => {
|
|
9
10
|
try {
|
|
10
11
|
const credentials = await getStoredCredentials();
|
|
11
12
|
console.log(section('Active session'));
|
|
12
13
|
if (credentials.email)
|
|
13
14
|
logger.label('Email', credentials.email);
|
|
14
|
-
logger.label('Workspace', credentials.workspace);
|
|
15
|
+
logger.label('Workspace', credentials.workspaceName ? `${credentials.workspaceName} (${credentials.workspace})` : credentials.workspace);
|
|
15
16
|
logger.label('Zone', credentials.zone === 'LA' ? 'Latin America (LA)' : 'Europe (EU)');
|
|
16
17
|
logger.label('API', credentials.zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com');
|
|
18
|
+
if (credentials.source === 'env')
|
|
19
|
+
logger.label('Origen', 'variables de entorno (PLAZBOT_API_KEY)');
|
|
17
20
|
console.log();
|
|
18
21
|
}
|
|
19
22
|
catch {
|
|
20
|
-
logger.error("No
|
|
23
|
+
logger.error("No hay una sesión activa. Ejecuta 'plazbot login' para iniciar sesión.");
|
|
21
24
|
process.exit(1);
|
|
22
25
|
}
|
|
23
26
|
});
|
|
24
27
|
addExamples(statusCommand, [
|
|
25
28
|
{ description: 'Show the active workspace, email and zone',
|
|
26
|
-
command: 'plazbot
|
|
29
|
+
command: 'plazbot whoami' },
|
|
27
30
|
]);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { apiBaseUrl, getStoredCredentials, saveCredentials } from '../../utils/credentials.js';
|
|
5
|
+
import { createSpinner, createTable, theme } from '../../utils/ui.js';
|
|
6
|
+
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { addExamples } from '../../utils/help.js';
|
|
8
|
+
async function fetchWorkspaces(dev) {
|
|
9
|
+
const creds = await getStoredCredentials();
|
|
10
|
+
if (!creds.userId) {
|
|
11
|
+
throw new Error("Esta sesión no tiene usuario asociado. Vuelve a iniciar sesión con 'plazbot login'.");
|
|
12
|
+
}
|
|
13
|
+
const res = await axios.get(`${apiBaseUrl(creds.zone, dev)}/api/workspace/workspaceByUserId/${encodeURIComponent(creds.userId)}`, {
|
|
14
|
+
headers: { Authorization: `Bearer ${creds.apiKey}`, 'x-workspace-id': creds.workspace },
|
|
15
|
+
});
|
|
16
|
+
return (Array.isArray(res.data) ? res.data : [])
|
|
17
|
+
.filter((w) => w.isInvitationAccepted && (w.state == 1 || !w.state) && w.data?.id)
|
|
18
|
+
.map((w) => ({ id: w.data.id, name: w.data.name || w.data.id }));
|
|
19
|
+
}
|
|
20
|
+
const listCommand = new Command('list')
|
|
21
|
+
.description('Lista tus workspaces')
|
|
22
|
+
.option('--json', 'Salida en JSON')
|
|
23
|
+
.option('--dev', 'Usar el entorno de desarrollo local', false)
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
try {
|
|
26
|
+
const creds = await getStoredCredentials();
|
|
27
|
+
const spinner = options.json ? null : createSpinner('Cargando workspaces...').start();
|
|
28
|
+
const items = await fetchWorkspaces(options.dev);
|
|
29
|
+
spinner?.stop();
|
|
30
|
+
if (options.json) {
|
|
31
|
+
console.log(JSON.stringify(items.map(w => ({ ...w, active: w.id === creds.workspace })), null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log(createTable(['', 'Workspace', 'ID'], items.map(w => [
|
|
35
|
+
w.id === creds.workspace ? chalk.green('●') : ' ', w.name, theme.muted(w.id),
|
|
36
|
+
])));
|
|
37
|
+
console.log(theme.muted(` Cambia con: plazbot workspace use <id>`));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
logger.error(error instanceof Error ? error.message : 'Error desconocido');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const useCommand = new Command('use')
|
|
45
|
+
.description('Cambia el workspace activo')
|
|
46
|
+
.argument('<workspaceId>', 'ID del workspace (ver plazbot workspace list)')
|
|
47
|
+
.option('--dev', 'Usar el entorno de desarrollo local', false)
|
|
48
|
+
.action(async (workspaceId, options) => {
|
|
49
|
+
try {
|
|
50
|
+
const creds = await getStoredCredentials();
|
|
51
|
+
if (creds.source === 'env') {
|
|
52
|
+
throw new Error('La sesión viene de variables de entorno; cambia PLAZBOT_WORKSPACE_ID.');
|
|
53
|
+
}
|
|
54
|
+
const items = await fetchWorkspaces(options.dev);
|
|
55
|
+
const target = items.find(w => w.id === workspaceId);
|
|
56
|
+
if (!target)
|
|
57
|
+
throw new Error(`No tienes acceso al workspace ${workspaceId}.`);
|
|
58
|
+
await saveCredentials({ ...creds, workspace: target.id, workspaceName: target.name });
|
|
59
|
+
logger.success(`Workspace activo: ${target.name}`);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
logger.error(error instanceof Error ? error.message : 'Error desconocido');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
export const workspaceCommand = new Command('workspace')
|
|
67
|
+
.description('Ver y cambiar el workspace activo')
|
|
68
|
+
.addCommand(listCommand)
|
|
69
|
+
.addCommand(useCommand);
|
|
70
|
+
addExamples(workspaceCommand, [
|
|
71
|
+
{ description: 'Listar tus workspaces', command: 'plazbot workspace list' },
|
|
72
|
+
{ description: 'Cambiar el workspace activo', command: 'plazbot workspace use wok_AbcDef123' },
|
|
73
|
+
]);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { apiBaseUrl } from '../../utils/credentials.js';
|
|
5
|
+
import { openBrowser } from '../../utils/open-browser.js';
|
|
6
|
+
/**
|
|
7
|
+
* Lógica compartida de conexión de números de WhatsApp (connect y setup).
|
|
8
|
+
* Mismo flujo que el panel: link de onboarding co.plzb.link → el usuario completa
|
|
9
|
+
* el registro de Meta → se detecta la integración nueva → se activa.
|
|
10
|
+
*/
|
|
11
|
+
export const ONBOARDING_SHORT_DOMAIN = 'https://co.plzb.link';
|
|
12
|
+
// Las integraciones de WhatsApp API y WhatsApp Business (coexistencia) se guardan como "whatsapp";
|
|
13
|
+
// "whatsapp_business" se incluye por compatibilidad (mismo criterio que Studio).
|
|
14
|
+
const WHATSAPP_TYPES = ['whatsapp', 'whatsapp_business'];
|
|
15
|
+
export function apiHeaders(creds) {
|
|
16
|
+
return {
|
|
17
|
+
Authorization: `Bearer ${creds.apiKey}`,
|
|
18
|
+
'x-workspace-id': creds.workspace,
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function integrationLabel(ig) {
|
|
23
|
+
const phone = ig.cellphoneNumberFormat || ig.cellphoneNumber || ig.id;
|
|
24
|
+
const name = ig.aliasName || ig.srcName;
|
|
25
|
+
return name ? `${name} (${phone})` : phone;
|
|
26
|
+
}
|
|
27
|
+
/** Todas las integraciones del workspace (lanza Error con mensaje claro si falla). */
|
|
28
|
+
export async function fetchIntegrations(creds, dev = false) {
|
|
29
|
+
try {
|
|
30
|
+
const res = await axios.get(`${apiBaseUrl(creds.zone, dev)}/api/workspace/${creds.workspace}`, { headers: apiHeaders(creds) });
|
|
31
|
+
const ws = Array.isArray(res.data) ? res.data[0] : res.data;
|
|
32
|
+
return ws?.integrations || [];
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
|
36
|
+
throw new Error("Tu sesión no es válida o fue revocada. Ejecuta 'plazbot login'.");
|
|
37
|
+
}
|
|
38
|
+
throw new Error('No se pudieron cargar las integraciones del workspace.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function whatsappNumbers(all) {
|
|
42
|
+
return all.filter(ig => WHATSAPP_TYPES.includes(ig.type));
|
|
43
|
+
}
|
|
44
|
+
/** Link corto de onboarding: reutiliza el vigente, regenera si venció o crea uno nuevo. */
|
|
45
|
+
export async function getOnboardingLink(creds, business, dev = false) {
|
|
46
|
+
const base = apiBaseUrl(creds.zone, dev);
|
|
47
|
+
const headers = apiHeaders(creds);
|
|
48
|
+
const type = business ? 'whatsapp_business' : 'whatsapp';
|
|
49
|
+
let link = null;
|
|
50
|
+
try {
|
|
51
|
+
link = (await axios.get(`${base}/api/workspace/${creds.workspace}/onboarding-link`, { headers, params: { type } })).data?.data ?? null;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
link = null;
|
|
55
|
+
}
|
|
56
|
+
if (link?.expiresAt && new Date(link.expiresAt) < new Date()) {
|
|
57
|
+
try {
|
|
58
|
+
link = (await axios.post(`${base}/api/workspace/${creds.workspace}/onboarding-link/regenerate`, { type }, { headers })).data?.data ?? null;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
link = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!link?.token) {
|
|
65
|
+
try {
|
|
66
|
+
link = (await axios.post(`${base}/api/workspace/${creds.workspace}/onboarding-link`, { type }, { headers })).data?.data ?? null;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
|
70
|
+
throw new Error("Tu sesión no es válida o fue revocada. Ejecuta 'plazbot login'.");
|
|
71
|
+
}
|
|
72
|
+
link = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!link?.token)
|
|
76
|
+
throw new Error('No se pudo obtener el link de conexión.');
|
|
77
|
+
return { url: link.shortUrl || `${ONBOARDING_SHORT_DOMAIN}/${link.token}`, expiresAt: link.expiresAt };
|
|
78
|
+
}
|
|
79
|
+
export async function activateIntegration(creds, integrationId, dev = false) {
|
|
80
|
+
try {
|
|
81
|
+
const res = await axios.post(`${apiBaseUrl(creds.zone, dev)}/api/workspace/${creds.workspace}/integrations/${integrationId}/activate`, { isActive: true, platformCode: 'whatsapp' }, { headers: apiHeaders(creds) });
|
|
82
|
+
return res.data?.success === true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Muestra el link, abre el navegador y espera (hasta `timeoutMs`) a que aparezca un número
|
|
90
|
+
* nuevo en el workspace. Lo activa y lo devuelve; null si venció el tiempo.
|
|
91
|
+
*/
|
|
92
|
+
export async function connectNewNumber(creds, opts) {
|
|
93
|
+
const dev = opts.dev ?? false;
|
|
94
|
+
const before = await fetchIntegrations(creds, dev);
|
|
95
|
+
const prevIds = new Set(before.map(i => i.id));
|
|
96
|
+
const { url, expiresAt } = await getOnboardingLink(creds, opts.business, dev);
|
|
97
|
+
const opened = opts.openInBrowser !== false && openBrowser(url);
|
|
98
|
+
const border = chalk.hex('#25D366');
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(border(' ┃ ') + chalk.bold(opened ? 'Abrimos el registro de Meta en tu navegador.' : 'Abre este link en tu navegador:'));
|
|
101
|
+
console.log(border(' ┃ ') + chalk.hex('#22d3ee').underline(url));
|
|
102
|
+
console.log(border(' ┃ ') + chalk.gray(opts.business
|
|
103
|
+
? 'Inicia sesión con Facebook y vincula tu app WhatsApp Business escaneando el QR.'
|
|
104
|
+
: 'Inicia sesión con Facebook, elige o crea tu cuenta de WhatsApp Business y verifica el número.'));
|
|
105
|
+
if (expiresAt)
|
|
106
|
+
console.log(border(' ┃ ') + chalk.gray(`El link vence el ${new Date(expiresAt).toLocaleDateString('es-ES', { day: 'numeric', month: 'long' })}.`));
|
|
107
|
+
console.log();
|
|
108
|
+
const spinner = ora({ text: chalk.gray('Esperando la conexión... (Ctrl+C para cancelar)'), spinner: 'dots', color: 'cyan' }).start();
|
|
109
|
+
const intervalMs = 5000;
|
|
110
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 10 * 60 * 1000);
|
|
111
|
+
const startedAt = Date.now();
|
|
112
|
+
while (Date.now() < deadline) {
|
|
113
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
114
|
+
try {
|
|
115
|
+
const fresh = whatsappNumbers(await fetchIntegrations(creds, dev)).filter(ig => !prevIds.has(ig.id));
|
|
116
|
+
if (fresh.length > 0) {
|
|
117
|
+
const detected = fresh[0];
|
|
118
|
+
spinner.succeed(chalk.hex('#4ade80')(`Número detectado: ${integrationLabel(detected)}`));
|
|
119
|
+
const act = ora({ text: chalk.gray('Activando el número...'), spinner: 'dots', color: 'green' }).start();
|
|
120
|
+
if (await activateIntegration(creds, detected.id, dev)) {
|
|
121
|
+
act.succeed(chalk.hex('#4ade80')('Número activo'));
|
|
122
|
+
detected.isActive = 1;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
act.warn(chalk.hex('#FFA726')('No se pudo activar automáticamente. Actívalo en el panel → Integraciones.'));
|
|
126
|
+
}
|
|
127
|
+
return detected;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// error puntual: seguir esperando
|
|
132
|
+
}
|
|
133
|
+
spinner.text = chalk.gray(`Esperando la conexión... ${Math.floor((Date.now() - startedAt) / 1000)}s (Ctrl+C para cancelar)`);
|
|
134
|
+
}
|
|
135
|
+
spinner.warn(chalk.hex('#FFA726')('No detectamos una conexión nueva a tiempo.'));
|
|
136
|
+
return null;
|
|
137
|
+
}
|