plazbot-cli 0.2.17 → 0.2.19

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,352 @@
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.crossCopyCommand = exports.exportCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const plazbot_1 = require("plazbot");
9
+ const credentials_1 = require("../../utils/credentials");
10
+ const logger_1 = require("../../utils/logger");
11
+ const ui_1 = require("../../utils/ui");
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const inquirer_1 = __importDefault(require("inquirer"));
16
+ // Campos validos segun el schema de agentValidation.ts (additionalProperties: false)
17
+ const VALID_AGENT_FIELDS = new Set([
18
+ 'name', 'description', 'prompt', 'zone', 'timezone', 'buffer',
19
+ 'color', 'question', 'enable', 'tags', 'showInChat',
20
+ 'instructions', 'person', 'fallbacks', 'rules',
21
+ 'examples', 'channels', 'services', 'actions',
22
+ 'useToolCalling', 'customAIConfig', 'aiProviders',
23
+ 'responseMode', 'batchWaitSeconds',
24
+ 'enableWidget', 'enableWhatsappWidget', 'urlWhatsappWidget',
25
+ 'formWidget', 'darkWidget', 'nameWidget', 'initialShowWidget',
26
+ 'fUseAutomationFlowWidget', 'iconWidget',
27
+ 'enableAvatarWidget', 'avatarGender', 'avatarVoiceId', 'avatarLanguage',
28
+ ]);
29
+ // Campos que el schema espera como string (no pueden ser null)
30
+ const STRING_FIELDS = new Set([
31
+ 'name', 'description', 'prompt', 'zone', 'timezone', 'color',
32
+ 'question', 'responseMode', 'iconWidget', 'avatarGender',
33
+ 'avatarVoiceId', 'avatarLanguage', 'urlWhatsappWidget', 'nameWidget',
34
+ ]);
35
+ // Campos que el schema espera como array (no pueden ser null)
36
+ const ARRAY_FIELDS = new Set([
37
+ 'tags', 'examples', 'channels', 'services', 'actions', 'aiProviders',
38
+ ]);
39
+ function cleanRequiredFields(fields) {
40
+ if (!fields || !Array.isArray(fields))
41
+ return [];
42
+ return fields.map((f) => {
43
+ const clean = {};
44
+ if (f.name)
45
+ clean.name = f.name;
46
+ if (f.description)
47
+ clean.description = f.description;
48
+ if (f.promptHint)
49
+ clean.promptHint = f.promptHint;
50
+ if (f.type)
51
+ clean.type = f.type;
52
+ // properties debe ser array o no existir
53
+ if (f.properties && Array.isArray(f.properties)) {
54
+ clean.properties = f.properties.map((p) => ({
55
+ name: p.name || '',
56
+ type: p.type || 'string',
57
+ }));
58
+ }
59
+ // Si properties es null o no es array, no incluirlo
60
+ return clean;
61
+ });
62
+ }
63
+ function cleanServices(services) {
64
+ if (!services || !Array.isArray(services))
65
+ return [];
66
+ return services.map((svc) => {
67
+ const clean = {
68
+ intent: svc.intent || '',
69
+ reference: svc.reference || '',
70
+ enabled: svc.enabled ?? true,
71
+ method: svc.method || 'GET',
72
+ endpoint: svc.endpoint || '',
73
+ requiredFields: cleanRequiredFields(svc.requiredFields),
74
+ };
75
+ if (svc.tags && Array.isArray(svc.tags))
76
+ clean.tags = svc.tags;
77
+ if (svc.headers && typeof svc.headers === 'object')
78
+ clean.headers = svc.headers;
79
+ if (svc.bodyTemplate && typeof svc.bodyTemplate === 'object')
80
+ clean.bodyTemplate = svc.bodyTemplate;
81
+ if (svc.bodySchema && typeof svc.bodySchema === 'object')
82
+ clean.bodySchema = svc.bodySchema;
83
+ if (svc.responseMapping && typeof svc.responseMapping === 'object')
84
+ clean.responseMapping = svc.responseMapping;
85
+ if (typeof svc.responseMessage === 'string')
86
+ clean.responseMessage = svc.responseMessage;
87
+ if (svc.responseConditions && Array.isArray(svc.responseConditions)) {
88
+ clean.responseConditions = svc.responseConditions.map((rc) => {
89
+ const c = { condition: rc.condition || '' };
90
+ if (typeof rc.message === 'string')
91
+ c.message = rc.message;
92
+ if (typeof rc.nextService === 'string')
93
+ c.nextService = rc.nextService;
94
+ return c;
95
+ });
96
+ }
97
+ if (typeof svc.action === 'string')
98
+ clean.action = svc.action;
99
+ return clean;
100
+ });
101
+ }
102
+ function cleanActions(actions) {
103
+ if (!actions || !Array.isArray(actions))
104
+ return [];
105
+ return actions.map((act) => {
106
+ const clean = {
107
+ intent: act.intent || '',
108
+ reference: act.reference || '',
109
+ enabled: act.enabled ?? true,
110
+ action: Array.isArray(act.action) ? act.action.map((a) => ({
111
+ type: a.type || '',
112
+ value: a.value ?? '',
113
+ })) : [],
114
+ };
115
+ if (act.tags && Array.isArray(act.tags))
116
+ clean.tags = act.tags;
117
+ clean.requiredFields = cleanRequiredFields(act.requiredFields);
118
+ if (typeof act.responseMessage === 'string')
119
+ clean.responseMessage = act.responseMessage;
120
+ if (typeof act.responseJson === 'boolean')
121
+ clean.responseJson = act.responseJson;
122
+ return clean;
123
+ });
124
+ }
125
+ function cleanAgentForExport(agentData) {
126
+ const cleaned = {};
127
+ for (const key of Object.keys(agentData)) {
128
+ if (!VALID_AGENT_FIELDS.has(key))
129
+ continue;
130
+ const value = agentData[key];
131
+ // Eliminar campos null en campos string
132
+ if (STRING_FIELDS.has(key)) {
133
+ if (value !== null && value !== undefined) {
134
+ cleaned[key] = String(value);
135
+ }
136
+ continue;
137
+ }
138
+ // Asegurar que campos array sean arrays
139
+ if (ARRAY_FIELDS.has(key)) {
140
+ if (key === 'services') {
141
+ cleaned[key] = cleanServices(value);
142
+ }
143
+ else if (key === 'actions') {
144
+ cleaned[key] = cleanActions(value);
145
+ }
146
+ else if (Array.isArray(value)) {
147
+ cleaned[key] = value;
148
+ }
149
+ else {
150
+ cleaned[key] = [];
151
+ }
152
+ continue;
153
+ }
154
+ // Eliminar nulls genericos
155
+ if (value !== null && value !== undefined) {
156
+ cleaned[key] = value;
157
+ }
158
+ }
159
+ // Limpiar channels
160
+ if (cleaned.channels && Array.isArray(cleaned.channels)) {
161
+ cleaned.channels = cleaned.channels.map((ch) => ({
162
+ channel: ch.channel || '',
163
+ key: ch.key || '',
164
+ multianswer: ch.multianswer || false,
165
+ }));
166
+ }
167
+ // Limpiar aiProviders
168
+ if (cleaned.aiProviders && Array.isArray(cleaned.aiProviders)) {
169
+ cleaned.aiProviders = cleaned.aiProviders.map((ai) => ({
170
+ provider: ai.provider,
171
+ model: ai.model,
172
+ apiToken: ai.apiToken || '',
173
+ temperature: ai.temperature,
174
+ maxTokens: ai.maxTokens,
175
+ isDefault: ai.isDefault,
176
+ }));
177
+ }
178
+ // Limpiar examples
179
+ if (cleaned.examples && Array.isArray(cleaned.examples)) {
180
+ cleaned.examples = cleaned.examples.map((ex) => ({
181
+ value: ex.value || '',
182
+ color: ex.color || 'blue',
183
+ }));
184
+ }
185
+ return cleaned;
186
+ }
187
+ exports.exportCommand = new commander_1.Command('export')
188
+ .description('Exportar configuracion limpia de un agente (compatible con importacion)')
189
+ .argument('<agentId>', 'ID del agente a exportar')
190
+ .option('-w, --workspace <id>', 'Workspace ID origen (sobreescribe config local)')
191
+ .option('-z, --zone <zone>', 'Zona LA o EU (sobreescribe config local)')
192
+ .option('-o, --output <path>', 'Ruta del archivo de salida (.json)')
193
+ .option('--no-channels', 'Excluir canales de la exportacion')
194
+ .option('--no-ai', 'Excluir configuracion de IA personalizada')
195
+ .option('--dev', 'Usar ambiente de desarrollo', false)
196
+ .action(async (agentId, options) => {
197
+ try {
198
+ const credentials = await (0, credentials_1.getStoredCredentials)();
199
+ const effectiveWorkspace = options.workspace || credentials.workspace;
200
+ const effectiveZone = (options.zone?.toUpperCase() === 'EU' ? 'EU' : options.zone?.toUpperCase() === 'LA' ? 'LA' : credentials.zone);
201
+ if (options.workspace || options.zone) {
202
+ console.log(chalk_1.default.hex('#FFA726')(`\n Modo soporte: workspace=${effectiveWorkspace} zona=${effectiveZone}`));
203
+ }
204
+ const agent = new plazbot_1.Agent({
205
+ workspaceId: effectiveWorkspace,
206
+ apiKey: credentials.apiKey,
207
+ zone: effectiveZone,
208
+ ...(options.dev && { customUrl: 'http://localhost:5090' }),
209
+ });
210
+ const spinner = (0, ui_1.createSpinner)('Obteniendo agente...');
211
+ spinner.start();
212
+ const response = await agent.getAgentById({ id: agentId });
213
+ const agentData = response.agent || response.data || response;
214
+ spinner.succeed('Agente obtenido');
215
+ // Limpiar para exportacion
216
+ const cleaned = cleanAgentForExport(agentData);
217
+ // Aplicar opciones de exclusion
218
+ if (!options.channels) {
219
+ delete cleaned.channels;
220
+ }
221
+ if (!options.ai) {
222
+ cleaned.customAIConfig = false;
223
+ delete cleaned.aiProviders;
224
+ }
225
+ const jsonOutput = JSON.stringify(cleaned, null, 2);
226
+ // Guardar en archivo o mostrar en consola
227
+ if (options.output) {
228
+ const outputPath = path_1.default.resolve(options.output);
229
+ fs_1.default.writeFileSync(outputPath, jsonOutput, 'utf-8');
230
+ console.log(chalk_1.default.hex('#4ade80')(`\n Exportado a: ${outputPath}`));
231
+ }
232
+ else {
233
+ console.log();
234
+ console.log(jsonOutput);
235
+ }
236
+ console.log();
237
+ console.log(chalk_1.default.gray(' Este JSON es compatible con:'));
238
+ console.log(chalk_1.default.gray(' - plazbot agent create <archivo.json>'));
239
+ console.log(chalk_1.default.gray(' - Importar JSON en la plataforma (AgentTraining)'));
240
+ console.log();
241
+ }
242
+ catch (error) {
243
+ const message = error instanceof Error ? error.message : 'Error desconocido';
244
+ logger_1.logger.error(message);
245
+ process.exit(1);
246
+ }
247
+ });
248
+ exports.crossCopyCommand = new commander_1.Command('cross-copy')
249
+ .description('Copiar un agente de un workspace a otro')
250
+ .argument('<agentId>', 'ID del agente a copiar')
251
+ .requiredOption('--from-workspace <id>', 'Workspace ID de origen')
252
+ .requiredOption('--to-workspace <id>', 'Workspace ID de destino')
253
+ .option('--from-zone <zone>', 'Zona del workspace origen (LA o EU)', 'LA')
254
+ .option('--to-zone <zone>', 'Zona del workspace destino (LA o EU)', 'LA')
255
+ .option('--no-channels', 'No copiar canales (recomendado)')
256
+ .option('--no-ai', 'No copiar configuracion de IA personalizada')
257
+ .option('--dev', 'Usar ambiente de desarrollo', false)
258
+ .action(async (agentId, options) => {
259
+ try {
260
+ const credentials = await (0, credentials_1.getStoredCredentials)();
261
+ const fromZone = (options.fromZone.toUpperCase() === 'EU' ? 'EU' : 'LA');
262
+ const toZone = (options.toZone.toUpperCase() === 'EU' ? 'EU' : 'LA');
263
+ console.log();
264
+ console.log(chalk_1.default.hex('#4CAF50')(' ┌' + '─'.repeat(58) + '┐'));
265
+ console.log(chalk_1.default.hex('#4CAF50')(' │') + chalk_1.default.bold(' Copiar agente entre workspaces').padEnd(68) + chalk_1.default.hex('#4CAF50')('│'));
266
+ console.log(chalk_1.default.hex('#4CAF50')(' └' + '─'.repeat(58) + '┘'));
267
+ console.log();
268
+ console.log(chalk_1.default.gray(` Origen: ${options.fromWorkspace} (${fromZone})`));
269
+ console.log(chalk_1.default.gray(` Destino: ${options.toWorkspace} (${toZone})`));
270
+ console.log(chalk_1.default.gray(` Agente: ${agentId}`));
271
+ console.log();
272
+ // 1. Obtener agente del workspace origen
273
+ const spinnerGet = (0, ui_1.createSpinner)('Obteniendo agente del workspace origen...');
274
+ spinnerGet.start();
275
+ const sourceAgent = new plazbot_1.Agent({
276
+ workspaceId: options.fromWorkspace,
277
+ apiKey: credentials.apiKey,
278
+ zone: fromZone,
279
+ ...(options.dev && { customUrl: 'http://localhost:5090' }),
280
+ });
281
+ const response = await sourceAgent.getAgentById({ id: agentId });
282
+ const agentData = response.agent || response.data || response;
283
+ if (!agentData || !agentData.name) {
284
+ spinnerGet.fail('No se encontro el agente en el workspace origen');
285
+ process.exit(1);
286
+ }
287
+ spinnerGet.succeed(`Agente obtenido: ${agentData.name}`);
288
+ // 2. Limpiar JSON
289
+ const cleaned = cleanAgentForExport(agentData);
290
+ // Ajustar zona al destino
291
+ cleaned.zone = toZone;
292
+ // Limpiar canales (los canales son especificos del workspace)
293
+ if (!options.channels) {
294
+ cleaned.channels = [];
295
+ }
296
+ // Limpiar AI config si se solicita
297
+ if (!options.ai) {
298
+ cleaned.customAIConfig = false;
299
+ delete cleaned.aiProviders;
300
+ }
301
+ // Desactivar widget (seguridad)
302
+ cleaned.enableWidget = false;
303
+ // 3. Mostrar resumen
304
+ console.log();
305
+ console.log(chalk_1.default.bold(' Configuracion a copiar:'));
306
+ console.log(chalk_1.default.gray(` Nombre: ${cleaned.name}`));
307
+ console.log(chalk_1.default.gray(` Zona destino: ${cleaned.zone}`));
308
+ console.log(chalk_1.default.gray(` Canales: ${cleaned.channels?.length || 0}`));
309
+ console.log(chalk_1.default.gray(` Servicios: ${cleaned.services?.length || 0}`));
310
+ console.log(chalk_1.default.gray(` Acciones: ${cleaned.actions?.length || 0}`));
311
+ console.log(chalk_1.default.gray(` AI Custom: ${cleaned.customAIConfig ? 'Si' : 'No'}`));
312
+ console.log();
313
+ const { confirm } = await inquirer_1.default.prompt([{
314
+ type: 'confirm',
315
+ name: 'confirm',
316
+ message: 'Crear este agente en el workspace destino?',
317
+ default: true,
318
+ }]);
319
+ if (!confirm) {
320
+ console.log(chalk_1.default.gray(' Operacion cancelada.'));
321
+ return;
322
+ }
323
+ // 4. Crear en workspace destino
324
+ const spinnerCreate = (0, ui_1.createSpinner)('Creando agente en workspace destino...');
325
+ spinnerCreate.start();
326
+ const targetAgent = new plazbot_1.Agent({
327
+ workspaceId: options.toWorkspace,
328
+ apiKey: credentials.apiKey,
329
+ zone: toZone,
330
+ ...(options.dev && { customUrl: 'http://localhost:5090' }),
331
+ });
332
+ const result = await targetAgent.addAgent(cleaned);
333
+ if (result?.success === false) {
334
+ spinnerCreate.fail(`Error: ${result.message || 'No se pudo crear el agente'}`);
335
+ process.exit(1);
336
+ }
337
+ const newId = result?.agentId || result?.id || result?.data?.agentId;
338
+ spinnerCreate.succeed('Agente creado exitosamente');
339
+ console.log();
340
+ if (newId) {
341
+ console.log(chalk_1.default.hex('#4ade80')(` Nuevo ID: ${newId}`));
342
+ }
343
+ console.log(chalk_1.default.gray(' Widget deshabilitado por defecto.'));
344
+ console.log(chalk_1.default.gray(' Canales deben configurarse manualmente en el workspace destino.'));
345
+ console.log();
346
+ }
347
+ catch (error) {
348
+ const message = error instanceof Error ? error.message : 'Error desconocido';
349
+ logger_1.logger.error(message);
350
+ process.exit(1);
351
+ }
352
+ });
@@ -17,6 +17,7 @@ const copy_1 = require("./copy");
17
17
  const files_1 = require("./files");
18
18
  const set_1 = require("./set");
19
19
  const monitor_1 = require("./monitor");
20
+ const export_1 = require("./export");
20
21
  exports.agentCommands = new commander_1.Command('agent')
21
22
  .description('Comandos relacionados con agentes de IA')
22
23
  .addCommand(list_1.listCommand)
@@ -33,4 +34,6 @@ exports.agentCommands = new commander_1.Command('agent')
33
34
  .addCommand(copy_1.copyCommand)
34
35
  .addCommand(files_1.filesCommand)
35
36
  .addCommand(set_1.setCommand)
36
- .addCommand(monitor_1.monitorCommand);
37
+ .addCommand(monitor_1.monitorCommand)
38
+ .addCommand(export_1.exportCommand)
39
+ .addCommand(export_1.crossCopyCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plazbot-cli",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "CLI para Plazbot SDK",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -0,0 +1,387 @@
1
+ import { Command } from 'commander';
2
+ import { Agent } from 'plazbot';
3
+ import { getStoredCredentials } from '../../utils/credentials';
4
+ import { logger } from '../../utils/logger';
5
+ import { createSpinner } from '../../utils/ui';
6
+ import chalk from 'chalk';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import inquirer from 'inquirer';
10
+
11
+ // Campos validos segun el schema de agentValidation.ts (additionalProperties: false)
12
+ const VALID_AGENT_FIELDS = new Set([
13
+ 'name', 'description', 'prompt', 'zone', 'timezone', 'buffer',
14
+ 'color', 'question', 'enable', 'tags', 'showInChat',
15
+ 'instructions', 'person', 'fallbacks', 'rules',
16
+ 'examples', 'channels', 'services', 'actions',
17
+ 'useToolCalling', 'customAIConfig', 'aiProviders',
18
+ 'responseMode', 'batchWaitSeconds',
19
+ 'enableWidget', 'enableWhatsappWidget', 'urlWhatsappWidget',
20
+ 'formWidget', 'darkWidget', 'nameWidget', 'initialShowWidget',
21
+ 'fUseAutomationFlowWidget', 'iconWidget',
22
+ 'enableAvatarWidget', 'avatarGender', 'avatarVoiceId', 'avatarLanguage',
23
+ ]);
24
+
25
+ // Campos que el schema espera como string (no pueden ser null)
26
+ const STRING_FIELDS = new Set([
27
+ 'name', 'description', 'prompt', 'zone', 'timezone', 'color',
28
+ 'question', 'responseMode', 'iconWidget', 'avatarGender',
29
+ 'avatarVoiceId', 'avatarLanguage', 'urlWhatsappWidget', 'nameWidget',
30
+ ]);
31
+
32
+ // Campos que el schema espera como array (no pueden ser null)
33
+ const ARRAY_FIELDS = new Set([
34
+ 'tags', 'examples', 'channels', 'services', 'actions', 'aiProviders',
35
+ ]);
36
+
37
+ function cleanRequiredFields(fields: any): any[] {
38
+ if (!fields || !Array.isArray(fields)) return [];
39
+ return fields.map((f: any) => {
40
+ const clean: any = {};
41
+ if (f.name) clean.name = f.name;
42
+ if (f.description) clean.description = f.description;
43
+ if (f.promptHint) clean.promptHint = f.promptHint;
44
+ if (f.type) clean.type = f.type;
45
+ // properties debe ser array o no existir
46
+ if (f.properties && Array.isArray(f.properties)) {
47
+ clean.properties = f.properties.map((p: any) => ({
48
+ name: p.name || '',
49
+ type: p.type || 'string',
50
+ }));
51
+ }
52
+ // Si properties es null o no es array, no incluirlo
53
+ return clean;
54
+ });
55
+ }
56
+
57
+ function cleanServices(services: any): any[] {
58
+ if (!services || !Array.isArray(services)) return [];
59
+ return services.map((svc: any) => {
60
+ const clean: any = {
61
+ intent: svc.intent || '',
62
+ reference: svc.reference || '',
63
+ enabled: svc.enabled ?? true,
64
+ method: svc.method || 'GET',
65
+ endpoint: svc.endpoint || '',
66
+ requiredFields: cleanRequiredFields(svc.requiredFields),
67
+ };
68
+ if (svc.tags && Array.isArray(svc.tags)) clean.tags = svc.tags;
69
+ if (svc.headers && typeof svc.headers === 'object') clean.headers = svc.headers;
70
+ if (svc.bodyTemplate && typeof svc.bodyTemplate === 'object') clean.bodyTemplate = svc.bodyTemplate;
71
+ if (svc.bodySchema && typeof svc.bodySchema === 'object') clean.bodySchema = svc.bodySchema;
72
+ if (svc.responseMapping && typeof svc.responseMapping === 'object') clean.responseMapping = svc.responseMapping;
73
+ if (typeof svc.responseMessage === 'string') clean.responseMessage = svc.responseMessage;
74
+ if (svc.responseConditions && Array.isArray(svc.responseConditions)) {
75
+ clean.responseConditions = svc.responseConditions.map((rc: any) => {
76
+ const c: any = { condition: rc.condition || '' };
77
+ if (typeof rc.message === 'string') c.message = rc.message;
78
+ if (typeof rc.nextService === 'string') c.nextService = rc.nextService;
79
+ return c;
80
+ });
81
+ }
82
+ if (typeof svc.action === 'string') clean.action = svc.action;
83
+ return clean;
84
+ });
85
+ }
86
+
87
+ function cleanActions(actions: any): any[] {
88
+ if (!actions || !Array.isArray(actions)) return [];
89
+ return actions.map((act: any) => {
90
+ const clean: any = {
91
+ intent: act.intent || '',
92
+ reference: act.reference || '',
93
+ enabled: act.enabled ?? true,
94
+ action: Array.isArray(act.action) ? act.action.map((a: any) => ({
95
+ type: a.type || '',
96
+ value: a.value ?? '',
97
+ })) : [],
98
+ };
99
+ if (act.tags && Array.isArray(act.tags)) clean.tags = act.tags;
100
+ clean.requiredFields = cleanRequiredFields(act.requiredFields);
101
+ if (typeof act.responseMessage === 'string') clean.responseMessage = act.responseMessage;
102
+ if (typeof act.responseJson === 'boolean') clean.responseJson = act.responseJson;
103
+ return clean;
104
+ });
105
+ }
106
+
107
+ function cleanAgentForExport(agentData: any): any {
108
+ const cleaned: any = {};
109
+
110
+ for (const key of Object.keys(agentData)) {
111
+ if (!VALID_AGENT_FIELDS.has(key)) continue;
112
+
113
+ const value = agentData[key];
114
+
115
+ // Eliminar campos null en campos string
116
+ if (STRING_FIELDS.has(key)) {
117
+ if (value !== null && value !== undefined) {
118
+ cleaned[key] = String(value);
119
+ }
120
+ continue;
121
+ }
122
+
123
+ // Asegurar que campos array sean arrays
124
+ if (ARRAY_FIELDS.has(key)) {
125
+ if (key === 'services') {
126
+ cleaned[key] = cleanServices(value);
127
+ } else if (key === 'actions') {
128
+ cleaned[key] = cleanActions(value);
129
+ } else if (Array.isArray(value)) {
130
+ cleaned[key] = value;
131
+ } else {
132
+ cleaned[key] = [];
133
+ }
134
+ continue;
135
+ }
136
+
137
+ // Eliminar nulls genericos
138
+ if (value !== null && value !== undefined) {
139
+ cleaned[key] = value;
140
+ }
141
+ }
142
+
143
+ // Limpiar channels
144
+ if (cleaned.channels && Array.isArray(cleaned.channels)) {
145
+ cleaned.channels = cleaned.channels.map((ch: any) => ({
146
+ channel: ch.channel || '',
147
+ key: ch.key || '',
148
+ multianswer: ch.multianswer || false,
149
+ }));
150
+ }
151
+
152
+ // Limpiar aiProviders
153
+ if (cleaned.aiProviders && Array.isArray(cleaned.aiProviders)) {
154
+ cleaned.aiProviders = cleaned.aiProviders.map((ai: any) => ({
155
+ provider: ai.provider,
156
+ model: ai.model,
157
+ apiToken: ai.apiToken || '',
158
+ temperature: ai.temperature,
159
+ maxTokens: ai.maxTokens,
160
+ isDefault: ai.isDefault,
161
+ }));
162
+ }
163
+
164
+ // Limpiar examples
165
+ if (cleaned.examples && Array.isArray(cleaned.examples)) {
166
+ cleaned.examples = cleaned.examples.map((ex: any) => ({
167
+ value: ex.value || '',
168
+ color: ex.color || 'blue',
169
+ }));
170
+ }
171
+
172
+ return cleaned;
173
+ }
174
+
175
+ export const exportCommand = new Command('export')
176
+ .description('Exportar configuracion limpia de un agente (compatible con importacion)')
177
+ .argument('<agentId>', 'ID del agente a exportar')
178
+ .option('-w, --workspace <id>', 'Workspace ID origen (sobreescribe config local)')
179
+ .option('-z, --zone <zone>', 'Zona LA o EU (sobreescribe config local)')
180
+ .option('-o, --output <path>', 'Ruta del archivo de salida (.json)')
181
+ .option('--no-channels', 'Excluir canales de la exportacion')
182
+ .option('--no-ai', 'Excluir configuracion de IA personalizada')
183
+ .option('--dev', 'Usar ambiente de desarrollo', false)
184
+ .action(async (agentId: string, options: {
185
+ workspace?: string;
186
+ zone?: string;
187
+ output?: string;
188
+ channels: boolean;
189
+ ai: boolean;
190
+ dev: boolean;
191
+ }) => {
192
+ try {
193
+ const credentials = await getStoredCredentials();
194
+
195
+ const effectiveWorkspace = options.workspace || credentials.workspace;
196
+ const effectiveZone = (options.zone?.toUpperCase() === 'EU' ? 'EU' : options.zone?.toUpperCase() === 'LA' ? 'LA' : credentials.zone) as 'LA' | 'EU';
197
+
198
+ if (options.workspace || options.zone) {
199
+ console.log(chalk.hex('#FFA726')(`\n Modo soporte: workspace=${effectiveWorkspace} zona=${effectiveZone}`));
200
+ }
201
+
202
+ const agent = new Agent({
203
+ workspaceId: effectiveWorkspace,
204
+ apiKey: credentials.apiKey,
205
+ zone: effectiveZone,
206
+ ...(options.dev && { customUrl: 'http://localhost:5090' }),
207
+ });
208
+
209
+ const spinner = createSpinner('Obteniendo agente...');
210
+ spinner.start();
211
+
212
+ const response: any = await agent.getAgentById({ id: agentId });
213
+ const agentData = response.agent || response.data || response;
214
+
215
+ spinner.succeed('Agente obtenido');
216
+
217
+ // Limpiar para exportacion
218
+ const cleaned = cleanAgentForExport(agentData);
219
+
220
+ // Aplicar opciones de exclusion
221
+ if (!options.channels) {
222
+ delete cleaned.channels;
223
+ }
224
+
225
+ if (!options.ai) {
226
+ cleaned.customAIConfig = false;
227
+ delete cleaned.aiProviders;
228
+ }
229
+
230
+ const jsonOutput = JSON.stringify(cleaned, null, 2);
231
+
232
+ // Guardar en archivo o mostrar en consola
233
+ if (options.output) {
234
+ const outputPath = path.resolve(options.output);
235
+ fs.writeFileSync(outputPath, jsonOutput, 'utf-8');
236
+ console.log(chalk.hex('#4ade80')(`\n Exportado a: ${outputPath}`));
237
+ } else {
238
+ console.log();
239
+ console.log(jsonOutput);
240
+ }
241
+
242
+ console.log();
243
+ console.log(chalk.gray(' Este JSON es compatible con:'));
244
+ console.log(chalk.gray(' - plazbot agent create <archivo.json>'));
245
+ console.log(chalk.gray(' - Importar JSON en la plataforma (AgentTraining)'));
246
+ console.log();
247
+
248
+ } catch (error) {
249
+ const message = error instanceof Error ? error.message : 'Error desconocido';
250
+ logger.error(message);
251
+ process.exit(1);
252
+ }
253
+ });
254
+
255
+ export const crossCopyCommand = new Command('cross-copy')
256
+ .description('Copiar un agente de un workspace a otro')
257
+ .argument('<agentId>', 'ID del agente a copiar')
258
+ .requiredOption('--from-workspace <id>', 'Workspace ID de origen')
259
+ .requiredOption('--to-workspace <id>', 'Workspace ID de destino')
260
+ .option('--from-zone <zone>', 'Zona del workspace origen (LA o EU)', 'LA')
261
+ .option('--to-zone <zone>', 'Zona del workspace destino (LA o EU)', 'LA')
262
+ .option('--no-channels', 'No copiar canales (recomendado)')
263
+ .option('--no-ai', 'No copiar configuracion de IA personalizada')
264
+ .option('--dev', 'Usar ambiente de desarrollo', false)
265
+ .action(async (agentId: string, options: {
266
+ fromWorkspace: string;
267
+ toWorkspace: string;
268
+ fromZone: string;
269
+ toZone: string;
270
+ channels: boolean;
271
+ ai: boolean;
272
+ dev: boolean;
273
+ }) => {
274
+ try {
275
+ const credentials = await getStoredCredentials();
276
+ const fromZone = (options.fromZone.toUpperCase() === 'EU' ? 'EU' : 'LA') as 'LA' | 'EU';
277
+ const toZone = (options.toZone.toUpperCase() === 'EU' ? 'EU' : 'LA') as 'LA' | 'EU';
278
+
279
+ console.log();
280
+ console.log(chalk.hex('#4CAF50')(' ┌' + '─'.repeat(58) + '┐'));
281
+ console.log(chalk.hex('#4CAF50')(' │') + chalk.bold(' Copiar agente entre workspaces').padEnd(68) + chalk.hex('#4CAF50')('│'));
282
+ console.log(chalk.hex('#4CAF50')(' └' + '─'.repeat(58) + '┘'));
283
+ console.log();
284
+ console.log(chalk.gray(` Origen: ${options.fromWorkspace} (${fromZone})`));
285
+ console.log(chalk.gray(` Destino: ${options.toWorkspace} (${toZone})`));
286
+ console.log(chalk.gray(` Agente: ${agentId}`));
287
+ console.log();
288
+
289
+ // 1. Obtener agente del workspace origen
290
+ const spinnerGet = createSpinner('Obteniendo agente del workspace origen...');
291
+ spinnerGet.start();
292
+
293
+ const sourceAgent = new Agent({
294
+ workspaceId: options.fromWorkspace,
295
+ apiKey: credentials.apiKey,
296
+ zone: fromZone,
297
+ ...(options.dev && { customUrl: 'http://localhost:5090' }),
298
+ });
299
+
300
+ const response: any = await sourceAgent.getAgentById({ id: agentId });
301
+ const agentData = response.agent || response.data || response;
302
+
303
+ if (!agentData || !agentData.name) {
304
+ spinnerGet.fail('No se encontro el agente en el workspace origen');
305
+ process.exit(1);
306
+ }
307
+
308
+ spinnerGet.succeed(`Agente obtenido: ${agentData.name}`);
309
+
310
+ // 2. Limpiar JSON
311
+ const cleaned = cleanAgentForExport(agentData);
312
+
313
+ // Ajustar zona al destino
314
+ cleaned.zone = toZone;
315
+
316
+ // Limpiar canales (los canales son especificos del workspace)
317
+ if (!options.channels) {
318
+ cleaned.channels = [];
319
+ }
320
+
321
+ // Limpiar AI config si se solicita
322
+ if (!options.ai) {
323
+ cleaned.customAIConfig = false;
324
+ delete cleaned.aiProviders;
325
+ }
326
+
327
+ // Desactivar widget (seguridad)
328
+ cleaned.enableWidget = false;
329
+
330
+ // 3. Mostrar resumen
331
+ console.log();
332
+ console.log(chalk.bold(' Configuracion a copiar:'));
333
+ console.log(chalk.gray(` Nombre: ${cleaned.name}`));
334
+ console.log(chalk.gray(` Zona destino: ${cleaned.zone}`));
335
+ console.log(chalk.gray(` Canales: ${cleaned.channels?.length || 0}`));
336
+ console.log(chalk.gray(` Servicios: ${cleaned.services?.length || 0}`));
337
+ console.log(chalk.gray(` Acciones: ${cleaned.actions?.length || 0}`));
338
+ console.log(chalk.gray(` AI Custom: ${cleaned.customAIConfig ? 'Si' : 'No'}`));
339
+ console.log();
340
+
341
+ const { confirm } = await (inquirer as any).prompt([{
342
+ type: 'confirm',
343
+ name: 'confirm',
344
+ message: 'Crear este agente en el workspace destino?',
345
+ default: true,
346
+ }]);
347
+
348
+ if (!confirm) {
349
+ console.log(chalk.gray(' Operacion cancelada.'));
350
+ return;
351
+ }
352
+
353
+ // 4. Crear en workspace destino
354
+ const spinnerCreate = createSpinner('Creando agente en workspace destino...');
355
+ spinnerCreate.start();
356
+
357
+ const targetAgent = new Agent({
358
+ workspaceId: options.toWorkspace,
359
+ apiKey: credentials.apiKey,
360
+ zone: toZone,
361
+ ...(options.dev && { customUrl: 'http://localhost:5090' }),
362
+ });
363
+
364
+ const result: any = await targetAgent.addAgent(cleaned);
365
+
366
+ if (result?.success === false) {
367
+ spinnerCreate.fail(`Error: ${result.message || 'No se pudo crear el agente'}`);
368
+ process.exit(1);
369
+ }
370
+
371
+ const newId = result?.agentId || result?.id || result?.data?.agentId;
372
+ spinnerCreate.succeed('Agente creado exitosamente');
373
+
374
+ console.log();
375
+ if (newId) {
376
+ console.log(chalk.hex('#4ade80')(` Nuevo ID: ${newId}`));
377
+ }
378
+ console.log(chalk.gray(' Widget deshabilitado por defecto.'));
379
+ console.log(chalk.gray(' Canales deben configurarse manualmente en el workspace destino.'));
380
+ console.log();
381
+
382
+ } catch (error) {
383
+ const message = error instanceof Error ? error.message : 'Error desconocido';
384
+ logger.error(message);
385
+ process.exit(1);
386
+ }
387
+ });
@@ -14,6 +14,7 @@ import { copyCommand } from './copy';
14
14
  import { filesCommand } from './files';
15
15
  import { setCommand } from './set';
16
16
  import { monitorCommand } from './monitor';
17
+ import { exportCommand, crossCopyCommand } from './export';
17
18
 
18
19
  export const agentCommands = new Command('agent')
19
20
  .description('Comandos relacionados con agentes de IA')
@@ -31,4 +32,6 @@ export const agentCommands = new Command('agent')
31
32
  .addCommand(copyCommand)
32
33
  .addCommand(filesCommand)
33
34
  .addCommand(setCommand)
34
- .addCommand(monitorCommand);
35
+ .addCommand(monitorCommand)
36
+ .addCommand(exportCommand)
37
+ .addCommand(crossCopyCommand);