plazbot-cli 0.4.1 → 0.5.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 +67 -111
- package/dist/cli.js +26 -11
- package/dist/commands/agent/ai-config.js +264 -63
- package/dist/commands/agent/chat.js +16 -11
- package/dist/commands/agent/copy.js +49 -13
- package/dist/commands/agent/create.js +62 -17
- package/dist/commands/agent/delete.js +50 -39
- package/dist/commands/agent/enable-widget.js +47 -27
- package/dist/commands/agent/export.js +180 -91
- package/dist/commands/agent/files.js +155 -73
- package/dist/commands/agent/get.js +25 -19
- package/dist/commands/agent/index.js +12 -10
- package/dist/commands/agent/list.js +20 -11
- package/dist/commands/agent/monitor.js +103 -57
- package/dist/commands/agent/on-message.js +55 -32
- package/dist/commands/agent/set.js +230 -82
- package/dist/commands/agent/templates.js +259 -156
- package/dist/commands/agent/tools.js +356 -123
- package/dist/commands/agent/update.js +69 -28
- package/dist/commands/agent/validate.js +72 -40
- package/dist/commands/agent/wizard.js +240 -329
- package/dist/commands/auth/browser-login.js +37 -31
- package/dist/commands/auth/login.js +49 -45
- package/dist/commands/auth/logout.js +20 -11
- package/dist/commands/auth/status.js +36 -12
- package/dist/commands/auth/workspace.js +61 -27
- package/dist/commands/portal/add-agent.js +106 -45
- package/dist/commands/portal/add-link.js +25 -28
- package/dist/commands/portal/clear-links.js +39 -19
- package/dist/commands/portal/create.js +66 -37
- package/dist/commands/portal/delete.js +40 -45
- package/dist/commands/portal/get.js +21 -54
- package/dist/commands/portal/index.js +12 -12
- package/dist/commands/portal/list.js +34 -52
- package/dist/commands/portal/shared.js +92 -0
- package/dist/commands/portal/update.js +100 -70
- package/dist/commands/whatsapp/broadcast.js +169 -77
- package/dist/commands/whatsapp/channels.js +167 -65
- package/dist/commands/whatsapp/chat.js +45 -46
- package/dist/commands/whatsapp/connect-flow.js +16 -15
- package/dist/commands/whatsapp/connect.js +36 -21
- package/dist/commands/whatsapp/delete-webhook.js +42 -25
- package/dist/commands/whatsapp/index.js +15 -16
- package/dist/commands/whatsapp/register-webhook.js +43 -26
- package/dist/commands/whatsapp/send-template.js +62 -61
- package/dist/commands/whatsapp/send.js +46 -33
- package/dist/commands/whatsapp/setup.js +62 -52
- package/dist/commands/whatsapp/shared.js +75 -0
- package/dist/commands/whatsapp/widget.js +82 -45
- package/dist/commands/workers/deploy.js +80 -58
- package/dist/commands/workers/index.js +8 -8
- package/dist/commands/workers/list.js +42 -20
- package/dist/commands/workers/logs.js +139 -36
- package/dist/commands/workers/remove.js +45 -47
- package/dist/commands/workers/secret.js +159 -83
- package/dist/commands/workers/test.js +108 -60
- package/dist/schemas/agent.config.schema.json +159 -8
- package/dist/studio/api/sseClient.js +1 -1
- package/dist/studio/api/studioApi.js +8 -0
- package/dist/studio/components/App.js +12 -0
- package/dist/studio/components/Footer.js +1 -1
- package/dist/studio/index.js +74 -14
- package/dist/studio/runOneShot.js +62 -23
- package/dist/studio/runRepl.js +5 -5
- package/dist/studio/slash/handlers.js +11 -8
- package/dist/studio/state/store.js +1 -1
- package/dist/utils/agent-errors.js +14 -11
- package/dist/utils/agent-patch.js +60 -0
- package/dist/utils/banner.js +9 -5
- package/dist/utils/confirm.js +31 -0
- package/dist/utils/credentials.js +13 -3
- package/dist/utils/logger.js +13 -10
- package/dist/utils/output.js +78 -0
- package/dist/utils/phone.js +22 -0
- package/dist/utils/ui.js +14 -11
- package/package.json +3 -3
|
@@ -3,12 +3,56 @@ import chalk from 'chalk';
|
|
|
3
3
|
import axios from 'axios';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import { theme, section, kvPair } from '../../utils/ui.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
6
|
+
import { requireInteractive } from '../../utils/confirm.js';
|
|
7
|
+
import { normalizePhone } from '../../utils/phone.js';
|
|
8
|
+
import { CliError } from '../../utils/output.js';
|
|
9
|
+
import { AI_MODELS, AI_PROVIDERS, DEFAULT_AI_MODEL, DEFAULT_TEMPERATURE, DEFAULT_MAX_TOKENS } from './ai-config.js';
|
|
10
|
+
import { promptService, promptAction } from './tools.js';
|
|
11
11
|
const COLORS = ['blue', 'green', 'orange', 'gray', 'white'];
|
|
12
|
+
// Labels are English; values stay as the platform stores them (the front uses the same values).
|
|
13
|
+
const TONES = [
|
|
14
|
+
{ name: 'Professional', value: 'profesional' },
|
|
15
|
+
{ name: 'Friendly', value: 'amigable' },
|
|
16
|
+
{ name: 'Formal', value: 'formal' },
|
|
17
|
+
{ name: 'Casual', value: 'casual' },
|
|
18
|
+
{ name: 'Technical', value: 'tecnico' },
|
|
19
|
+
{ name: 'Empathetic', value: 'empatico' },
|
|
20
|
+
];
|
|
21
|
+
const STYLES = [
|
|
22
|
+
{ name: 'Concise', value: 'conciso' },
|
|
23
|
+
{ name: 'Detailed', value: 'detallado' },
|
|
24
|
+
{ name: 'Conversational', value: 'conversacional' },
|
|
25
|
+
{ name: 'Direct', value: 'directo' },
|
|
26
|
+
];
|
|
27
|
+
const LANGUAGES = [
|
|
28
|
+
{ name: 'Spanish', value: 'Espanol' },
|
|
29
|
+
{ name: 'English', value: 'English' },
|
|
30
|
+
{ name: 'Portuguese', value: 'Portugues' },
|
|
31
|
+
{ name: 'French', value: 'Frances' },
|
|
32
|
+
];
|
|
33
|
+
// Default fallback messages are shown to end users, so they follow the agent language.
|
|
34
|
+
const FALLBACK_DEFAULTS = {
|
|
35
|
+
Espanol: {
|
|
36
|
+
noAnswer: 'Lo siento, no tengo información sobre eso. ¿Puedo ayudarte con otra consulta?',
|
|
37
|
+
serviceError: 'Disculpa, estamos experimentando dificultades técnicas. Intenta de nuevo en unos momentos.',
|
|
38
|
+
doNotUnderstand: 'No estoy seguro de entender tu consulta. ¿Podrías reformularla?',
|
|
39
|
+
},
|
|
40
|
+
English: {
|
|
41
|
+
noAnswer: "Sorry, I don't have information about that. Can I help you with something else?",
|
|
42
|
+
serviceError: "Sorry, we're having technical difficulties. Please try again in a few moments.",
|
|
43
|
+
doNotUnderstand: "I'm not sure I understand your question. Could you rephrase it?",
|
|
44
|
+
},
|
|
45
|
+
Portugues: {
|
|
46
|
+
noAnswer: 'Desculpe, não tenho informações sobre isso. Posso ajudar com outra dúvida?',
|
|
47
|
+
serviceError: 'Desculpe, estamos com dificuldades técnicas. Tente novamente em alguns instantes.',
|
|
48
|
+
doNotUnderstand: 'Não tenho certeza se entendi sua pergunta. Poderia reformulá-la?',
|
|
49
|
+
},
|
|
50
|
+
Frances: {
|
|
51
|
+
noAnswer: "Désolé, je n'ai pas d'informations à ce sujet. Puis-je vous aider avec autre chose ?",
|
|
52
|
+
serviceError: 'Désolé, nous rencontrons des difficultés techniques. Réessayez dans quelques instants.',
|
|
53
|
+
doNotUnderstand: "Je ne suis pas sûr de comprendre votre question. Pourriez-vous la reformuler ?",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
12
56
|
function getBaseUrl(zone, dev) {
|
|
13
57
|
if (dev)
|
|
14
58
|
return 'http://localhost:5090';
|
|
@@ -40,14 +84,9 @@ function getIntegrationKey(ig) {
|
|
|
40
84
|
return ig.aliasName || ig.srcName || ig.id;
|
|
41
85
|
}
|
|
42
86
|
async function fetchWorkspaceIntegrations(baseUrl, workspaceId, headers) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return wsData?.integrations || [];
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
87
|
+
const wkRes = await axios.get(`${baseUrl}/api/workspace/${workspaceId}`, { headers });
|
|
88
|
+
const wsData = Array.isArray(wkRes.data) ? wkRes.data[0] : wkRes.data;
|
|
89
|
+
return wsData?.integrations || [];
|
|
51
90
|
}
|
|
52
91
|
async function activateIntegration(baseUrl, workspaceId, integrationId, headers) {
|
|
53
92
|
try {
|
|
@@ -58,7 +97,61 @@ async function activateIntegration(baseUrl, workspaceId, integrationId, headers)
|
|
|
58
97
|
return false;
|
|
59
98
|
}
|
|
60
99
|
}
|
|
100
|
+
async function activateWithSpinner(baseUrl, workspaceId, integrationId, headers) {
|
|
101
|
+
const actSpinner = ora({ text: chalk.gray('Activating integration...'), spinner: 'dots', color: 'green' }).start();
|
|
102
|
+
const activated = await activateIntegration(baseUrl, workspaceId, integrationId, headers);
|
|
103
|
+
if (activated) {
|
|
104
|
+
actSpinner.succeed(chalk.hex('#4ade80')('Integration activated.'));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
actSpinner.warn(chalk.hex('#FFA726')('Could not activate the integration. Activate it from the platform (Settings > Integrations).'));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolves after `ms`, or earlier when the user presses Enter. Restores the terminal
|
|
112
|
+
* mode afterwards and keeps Ctrl+C working while stdin is in raw mode.
|
|
113
|
+
*/
|
|
114
|
+
function waitOrEnter(ms) {
|
|
115
|
+
let cleanup = () => { };
|
|
116
|
+
const promise = new Promise(resolve => {
|
|
117
|
+
const stdin = process.stdin;
|
|
118
|
+
const isTTY = !!stdin.isTTY;
|
|
119
|
+
const wasRaw = isTTY ? stdin.isRaw : false;
|
|
120
|
+
const timer = setTimeout(() => finish('timeout'), ms);
|
|
121
|
+
const onData = (data) => {
|
|
122
|
+
const s = data.toString();
|
|
123
|
+
if (s.includes('\u0003')) {
|
|
124
|
+
cleanup();
|
|
125
|
+
process.exit(130);
|
|
126
|
+
}
|
|
127
|
+
if (s.includes('\n') || s.includes('\r'))
|
|
128
|
+
finish('enter');
|
|
129
|
+
};
|
|
130
|
+
function finish(reason) {
|
|
131
|
+
cleanup();
|
|
132
|
+
resolve(reason);
|
|
133
|
+
}
|
|
134
|
+
cleanup = () => {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
stdin.removeListener('data', onData);
|
|
137
|
+
if (isTTY) {
|
|
138
|
+
try {
|
|
139
|
+
stdin.setRawMode(!!wasRaw);
|
|
140
|
+
}
|
|
141
|
+
catch { /* terminal already closed */ }
|
|
142
|
+
}
|
|
143
|
+
stdin.pause();
|
|
144
|
+
cleanup = () => { };
|
|
145
|
+
};
|
|
146
|
+
if (isTTY)
|
|
147
|
+
stdin.setRawMode(true);
|
|
148
|
+
stdin.resume();
|
|
149
|
+
stdin.on('data', onData);
|
|
150
|
+
});
|
|
151
|
+
return { promise, cancel: () => cleanup() };
|
|
152
|
+
}
|
|
61
153
|
export async function connectChannelFlow(ctx) {
|
|
154
|
+
requireInteractive('Connecting a channel', 'Connect channels later from the platform (Settings > Integrations).');
|
|
62
155
|
const channels = [];
|
|
63
156
|
const baseUrl = getBaseUrl(ctx.zone, ctx.dev);
|
|
64
157
|
const headers = {
|
|
@@ -76,20 +169,25 @@ export async function connectChannelFlow(ctx) {
|
|
|
76
169
|
}]);
|
|
77
170
|
const channelLabel = CHANNEL_CHOICES.find(c => c.value === channelType)?.name || channelType;
|
|
78
171
|
const targetType = channelType === 'whatsapp_business' ? 'whatsapp' : channelType;
|
|
79
|
-
//
|
|
172
|
+
// Load the workspace integrations of the selected type
|
|
80
173
|
const loadSpinner = ora({ text: chalk.gray('Loading integrations...'), spinner: 'dots', color: 'green' }).start();
|
|
81
|
-
|
|
174
|
+
let allIntegrations = [];
|
|
175
|
+
try {
|
|
176
|
+
allIntegrations = await fetchWorkspaceIntegrations(baseUrl, ctx.workspaceId, headers);
|
|
177
|
+
loadSpinner.stop();
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
loadSpinner.warn(chalk.hex('#FFA726')(`Could not load existing integrations (${err instanceof Error ? err.message : err}). You can still generate a connection link.`));
|
|
181
|
+
}
|
|
82
182
|
const existing = allIntegrations.filter((ig) => ig.type === targetType);
|
|
83
|
-
loadSpinner.stop();
|
|
84
183
|
let selectedIntegration = null;
|
|
85
184
|
if (existing.length > 0) {
|
|
86
|
-
// Mostrar integraciones existentes + opcion de agregar nueva
|
|
87
185
|
const choices = existing.map((ig) => ({
|
|
88
|
-
name: `${getIntegrationLabel(ig)}`,
|
|
186
|
+
name: `${getIntegrationLabel(ig)}${ig.isActive ? '' : chalk.gray(' (inactive)')}`,
|
|
89
187
|
value: ig.id,
|
|
90
188
|
}));
|
|
91
189
|
choices.push(new inquirer.Separator());
|
|
92
|
-
choices.push({ name: chalk.hex('#4ade80')('+
|
|
190
|
+
choices.push({ name: chalk.hex('#4ade80')('+ Connect a new one (generate link)'), value: '__new__' });
|
|
93
191
|
const { selected } = await inquirer.prompt([{
|
|
94
192
|
type: 'list',
|
|
95
193
|
name: 'selected',
|
|
@@ -97,19 +195,10 @@ export async function connectChannelFlow(ctx) {
|
|
|
97
195
|
choices,
|
|
98
196
|
}]);
|
|
99
197
|
if (selected !== '__new__') {
|
|
100
|
-
// Selecciono una existente
|
|
101
198
|
selectedIntegration = existing.find((ig) => ig.id === selected);
|
|
102
199
|
const label = getIntegrationLabel(selectedIntegration);
|
|
103
|
-
// Activar la integracion si no esta activa
|
|
104
200
|
if (!selectedIntegration.isActive) {
|
|
105
|
-
|
|
106
|
-
const activated = await activateIntegration(baseUrl, ctx.workspaceId, selectedIntegration.id, headers);
|
|
107
|
-
if (activated) {
|
|
108
|
-
actSpinner.succeed(chalk.hex('#4ade80')('Integration activated successfully.'));
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
actSpinner.warn(chalk.hex('#FFA726')('Could not activate the integration. You can activate it manually from the platform.'));
|
|
112
|
-
}
|
|
201
|
+
await activateWithSpinner(baseUrl, ctx.workspaceId, selectedIntegration.id, headers);
|
|
113
202
|
}
|
|
114
203
|
channels.push({
|
|
115
204
|
channel: CHANNEL_AGENT_MAP[channelType],
|
|
@@ -119,130 +208,87 @@ export async function connectChannelFlow(ctx) {
|
|
|
119
208
|
console.log(chalk.hex('#4ade80')(` Channel linked: ${label}`));
|
|
120
209
|
}
|
|
121
210
|
}
|
|
122
|
-
//
|
|
211
|
+
// No existing integrations, or "connect a new one": generate an onboarding link
|
|
123
212
|
if (!selectedIntegration) {
|
|
124
213
|
const spinner = ora({ text: chalk.gray('Generating connection link...'), spinner: 'dots', color: 'green' }).start();
|
|
125
214
|
try {
|
|
126
215
|
const linkRes = await axios.post(`${baseUrl}/api/workspace/${ctx.workspaceId}/onboarding-link`, { type: channelType }, { headers });
|
|
127
216
|
if (!linkRes.data?.success) {
|
|
128
|
-
spinner.fail(chalk.hex('#EF5350')(`
|
|
217
|
+
spinner.fail(chalk.hex('#EF5350')(`Could not generate the link: ${linkRes.data?.message || 'unknown error'}.`));
|
|
129
218
|
}
|
|
130
219
|
else {
|
|
131
220
|
const shortUrl = linkRes.data.data?.shortUrl || '';
|
|
132
221
|
spinner.stop();
|
|
133
|
-
// Mostrar link
|
|
134
222
|
console.log();
|
|
135
|
-
console.log(chalk.hex('#4CAF50')(' ─'.repeat(
|
|
136
|
-
console.log(chalk.bold(`
|
|
223
|
+
console.log(chalk.hex('#4CAF50')(' ' + '─'.repeat(60)));
|
|
224
|
+
console.log(chalk.bold(` Open this link (or send it to the account owner) to connect ${channelLabel}:`));
|
|
137
225
|
console.log();
|
|
138
226
|
console.log(' ' + chalk.hex('#22d3ee')(shortUrl));
|
|
139
227
|
console.log();
|
|
140
|
-
console.log(chalk.hex('#4CAF50')(' ─'.repeat(
|
|
228
|
+
console.log(chalk.hex('#4CAF50')(' ' + '─'.repeat(60)));
|
|
141
229
|
console.log();
|
|
142
|
-
//
|
|
230
|
+
// Remember current IDs to detect the new integration
|
|
143
231
|
const prevIds = new Set(allIntegrations.map((i) => i.id));
|
|
144
|
-
// Polling para detectar nueva integracion (Enter para saltar)
|
|
145
|
-
const pollSpinner = ora({ text: chalk.gray('Waiting for connection... (Enter to skip)'), spinner: 'dots', color: 'cyan' }).start();
|
|
146
232
|
const POLL_INTERVAL = 5000;
|
|
147
233
|
const MAX_POLLS = 60;
|
|
148
|
-
|
|
234
|
+
const pollSpinner = ora({ text: chalk.gray('Waiting for the connection... (press Enter to skip)'), spinner: 'dots', color: 'cyan' }).start();
|
|
235
|
+
const skip = waitOrEnter(POLL_INTERVAL * MAX_POLLS + 1000);
|
|
149
236
|
let skipped = false;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
skipped = true;
|
|
155
|
-
process.stdin.removeListener('data', onData);
|
|
156
|
-
if (wasRaw)
|
|
157
|
-
process.stdin.setRawMode(false);
|
|
158
|
-
resolve();
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
const wasRaw = process.stdin.isRaw;
|
|
162
|
-
if (process.stdin.isTTY)
|
|
163
|
-
process.stdin.setRawMode(true);
|
|
164
|
-
process.stdin.resume();
|
|
165
|
-
process.stdin.on('data', onData);
|
|
166
|
-
setTimeout(() => {
|
|
167
|
-
process.stdin.removeListener('data', onData);
|
|
168
|
-
if (process.stdin.isTTY && wasRaw !== undefined) {
|
|
169
|
-
try {
|
|
170
|
-
process.stdin.setRawMode(wasRaw);
|
|
171
|
-
}
|
|
172
|
-
catch { }
|
|
173
|
-
}
|
|
174
|
-
resolve();
|
|
175
|
-
}, POLL_INTERVAL * MAX_POLLS + 1000);
|
|
176
|
-
});
|
|
237
|
+
skip.promise.then(reason => { if (reason === 'enter')
|
|
238
|
+
skipped = true; });
|
|
239
|
+
let detected = null;
|
|
240
|
+
let pollErrors = 0;
|
|
177
241
|
for (let i = 0; i < MAX_POLLS; i++) {
|
|
178
|
-
await Promise.race([
|
|
179
|
-
new Promise(r => setTimeout(r, POLL_INTERVAL)),
|
|
180
|
-
skipPromise,
|
|
181
|
-
]);
|
|
242
|
+
await Promise.race([new Promise(r => setTimeout(r, POLL_INTERVAL)), skip.promise]);
|
|
182
243
|
if (skipped)
|
|
183
244
|
break;
|
|
184
245
|
try {
|
|
185
246
|
const freshIntegrations = await fetchWorkspaceIntegrations(baseUrl, ctx.workspaceId, headers);
|
|
186
247
|
const newOnes = freshIntegrations.filter((ig) => !prevIds.has(ig.id));
|
|
187
248
|
if (newOnes.length > 0) {
|
|
188
|
-
|
|
189
|
-
detected = match || newOnes[0];
|
|
249
|
+
detected = newOnes.find((ig) => ig.type === targetType) || newOnes[0];
|
|
190
250
|
break;
|
|
191
251
|
}
|
|
192
252
|
}
|
|
193
|
-
catch {
|
|
253
|
+
catch {
|
|
254
|
+
pollErrors++;
|
|
255
|
+
}
|
|
194
256
|
const elapsed = Math.floor(((i + 1) * POLL_INTERVAL) / 1000);
|
|
195
|
-
pollSpinner.text = chalk.gray(`Waiting for connection... ${elapsed}s (Enter to skip)`);
|
|
257
|
+
pollSpinner.text = chalk.gray(`Waiting for the connection... ${elapsed}s (press Enter to skip)${pollErrors ? ` · ${pollErrors} failed checks` : ''}`);
|
|
196
258
|
}
|
|
259
|
+
skip.cancel();
|
|
197
260
|
if (detected) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Activar la integracion detectada
|
|
201
|
-
const actSpinner = ora({ text: chalk.gray('Activating integration...'), spinner: 'dots', color: 'green' }).start();
|
|
202
|
-
const activated = await activateIntegration(baseUrl, ctx.workspaceId, detected.id, headers);
|
|
203
|
-
if (activated) {
|
|
204
|
-
actSpinner.succeed(chalk.hex('#4ade80')('Integration activated successfully.'));
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
actSpinner.warn(chalk.hex('#FFA726')('Could not activate the integration. You can activate it manually from the platform.'));
|
|
208
|
-
}
|
|
261
|
+
pollSpinner.succeed(chalk.hex('#4ade80')(`Connection detected: ${getIntegrationLabel(detected)}`));
|
|
262
|
+
await activateWithSpinner(baseUrl, ctx.workspaceId, detected.id, headers);
|
|
209
263
|
channels.push({
|
|
210
264
|
channel: CHANNEL_AGENT_MAP[channelType],
|
|
211
265
|
key: getIntegrationKey(detected),
|
|
212
266
|
multianswer: false,
|
|
213
267
|
});
|
|
214
|
-
console.log(chalk.hex('#4ade80')(' Channel linked
|
|
268
|
+
console.log(chalk.hex('#4ade80')(' Channel linked.'));
|
|
215
269
|
}
|
|
216
270
|
else {
|
|
217
|
-
pollSpinner.warn(chalk.hex('#FFA726')(skipped ? 'Skipped.' : '
|
|
271
|
+
pollSpinner.warn(chalk.hex('#FFA726')(skipped ? 'Skipped.' : 'Timed out: no new connection detected.'));
|
|
218
272
|
const { action } = await inquirer.prompt([{
|
|
219
273
|
type: 'list', name: 'action',
|
|
220
274
|
message: 'What would you like to do?',
|
|
221
275
|
choices: [
|
|
222
|
-
{ name: 'Enter ID manually', value: 'manual' },
|
|
223
|
-
{ name: 'Skip', value: 'skip' },
|
|
276
|
+
{ name: 'Enter the integration ID manually', value: 'manual' },
|
|
277
|
+
{ name: 'Skip this channel', value: 'skip' },
|
|
224
278
|
],
|
|
225
279
|
}]);
|
|
226
280
|
if (action === 'manual') {
|
|
227
281
|
const { key } = await inquirer.prompt([
|
|
228
|
-
{ type: 'input', name: 'key', message: `${channelLabel}
|
|
282
|
+
{ type: 'input', name: 'key', message: `${channelLabel} integration ID:`, validate: (v) => v.trim().length > 0 || 'Required' },
|
|
229
283
|
]);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const activated = await activateIntegration(baseUrl, ctx.workspaceId, key, headers);
|
|
233
|
-
if (activated) {
|
|
234
|
-
actSpinner.succeed(chalk.hex('#4ade80')('Integration activated successfully.'));
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
actSpinner.warn(chalk.hex('#FFA726')('Could not activate the integration. You can activate it manually from the platform.'));
|
|
238
|
-
}
|
|
239
|
-
channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key, multianswer: false });
|
|
284
|
+
await activateWithSpinner(baseUrl, ctx.workspaceId, key.trim(), headers);
|
|
285
|
+
channels.push({ channel: CHANNEL_AGENT_MAP[channelType], key: key.trim(), multianswer: false });
|
|
240
286
|
}
|
|
241
287
|
}
|
|
242
288
|
}
|
|
243
289
|
}
|
|
244
290
|
catch (err) {
|
|
245
|
-
spinner.fail(chalk.hex('#EF5350')(`
|
|
291
|
+
spinner.fail(chalk.hex('#EF5350')(`Could not generate the link: ${err instanceof Error ? err.message : err}.`));
|
|
246
292
|
}
|
|
247
293
|
}
|
|
248
294
|
const { more } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Connect another channel?', default: false }]);
|
|
@@ -250,22 +296,32 @@ export async function connectChannelFlow(ctx) {
|
|
|
250
296
|
}
|
|
251
297
|
return channels;
|
|
252
298
|
}
|
|
299
|
+
function validatePhone(v) {
|
|
300
|
+
try {
|
|
301
|
+
normalizePhone(v);
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
return e instanceof CliError ? e.message : 'Invalid phone number';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
253
308
|
export async function runAgentWizard(zone, workspaceId, apiKey, dev) {
|
|
254
|
-
|
|
255
|
-
console.log(
|
|
256
|
-
|
|
309
|
+
requireInteractive('The agent wizard', 'Pass a JSON file instead: plazbot agent create ./agent.json');
|
|
310
|
+
console.log(section('Create a new AI agent'));
|
|
311
|
+
console.log(theme.muted(' Answer a few questions to configure your agent. Press Enter to accept defaults.\n'));
|
|
312
|
+
// Step 1: basic information
|
|
257
313
|
console.log(theme.bold('\n Step 1/8: Basic information'));
|
|
258
314
|
const basic = await inquirer.prompt([
|
|
259
315
|
{
|
|
260
316
|
type: 'input',
|
|
261
317
|
name: 'name',
|
|
262
318
|
message: 'Agent name:',
|
|
263
|
-
validate: (v) => v.length > 0 || '
|
|
319
|
+
validate: (v) => v.trim().length > 0 || 'The name is required',
|
|
264
320
|
},
|
|
265
321
|
{
|
|
266
322
|
type: 'input',
|
|
267
323
|
name: 'description',
|
|
268
|
-
message: 'Short description:',
|
|
324
|
+
message: 'Short description (optional):',
|
|
269
325
|
default: '',
|
|
270
326
|
},
|
|
271
327
|
{
|
|
@@ -275,11 +331,15 @@ export async function runAgentWizard(zone, workspaceId, apiKey, dev) {
|
|
|
275
331
|
default: 'You are a friendly and professional virtual assistant.',
|
|
276
332
|
},
|
|
277
333
|
{
|
|
278
|
-
type: '
|
|
334
|
+
type: 'input',
|
|
279
335
|
name: 'buffer',
|
|
280
|
-
message: 'Conversation
|
|
281
|
-
default: 5,
|
|
282
|
-
validate: (v) =>
|
|
336
|
+
message: 'Conversation memory: how many previous messages the agent reads (3-20):',
|
|
337
|
+
default: '5',
|
|
338
|
+
validate: (v) => {
|
|
339
|
+
const n = Number(String(v).trim());
|
|
340
|
+
return (Number.isInteger(n) && n >= 3 && n <= 20) || 'Enter a whole number of messages between 3 and 20';
|
|
341
|
+
},
|
|
342
|
+
filter: (v) => String(v).trim(),
|
|
283
343
|
},
|
|
284
344
|
{
|
|
285
345
|
type: 'list',
|
|
@@ -289,283 +349,132 @@ export async function runAgentWizard(zone, workspaceId, apiKey, dev) {
|
|
|
289
349
|
default: 'blue',
|
|
290
350
|
},
|
|
291
351
|
]);
|
|
292
|
-
//
|
|
352
|
+
// Step 2: personality
|
|
293
353
|
console.log(theme.bold('\n Step 2/8: Personality and instructions'));
|
|
294
354
|
const personality = await inquirer.prompt([
|
|
295
|
-
{
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
type: 'list',
|
|
304
|
-
name: 'style',
|
|
305
|
-
message: 'Response style:',
|
|
306
|
-
choices: ['conciso', 'detallado', 'conversacional', 'directo'],
|
|
307
|
-
default: 'conciso',
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
type: 'input',
|
|
311
|
-
name: 'personality',
|
|
312
|
-
message: 'Personality in one sentence:',
|
|
313
|
-
default: 'Helpful and knowledgeable',
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
type: 'input',
|
|
317
|
-
name: 'objective',
|
|
318
|
-
message: 'Main objective of the agent:',
|
|
319
|
-
default: 'Help users with their queries',
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
type: 'list',
|
|
323
|
-
name: 'language',
|
|
324
|
-
message: 'Primary language:',
|
|
325
|
-
choices: ['Espanol', 'English', 'Portugues', 'Frances'],
|
|
326
|
-
default: 'Espanol',
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
type: 'confirm',
|
|
330
|
-
name: 'useEmojis',
|
|
331
|
-
message: 'Use emojis in responses?',
|
|
332
|
-
default: true,
|
|
333
|
-
},
|
|
355
|
+
{ type: 'list', name: 'tone', message: 'Tone:', choices: TONES, default: 'profesional' },
|
|
356
|
+
{ type: 'list', name: 'style', message: 'Response style:', choices: STYLES, default: 'conciso' },
|
|
357
|
+
{ type: 'input', name: 'personality', message: 'Personality in one sentence:', default: 'Helpful and knowledgeable' },
|
|
358
|
+
{ type: 'input', name: 'objective', message: 'Main objective of the agent:', default: 'Help users with their questions' },
|
|
359
|
+
{ type: 'list', name: 'language', message: 'Language the agent replies in:', choices: LANGUAGES, default: 'Espanol' },
|
|
360
|
+
{ type: 'confirm', name: 'useEmojis', message: 'Use emojis in replies?', default: true },
|
|
334
361
|
]);
|
|
335
|
-
//
|
|
362
|
+
// Step 3: identity
|
|
336
363
|
console.log(theme.bold('\n Step 3/8: Agent identity'));
|
|
337
364
|
const person = await inquirer.prompt([
|
|
338
|
-
{
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
message: 'Character name (how it introduces itself):',
|
|
342
|
-
default: basic.name,
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
type: 'input',
|
|
346
|
-
name: 'role',
|
|
347
|
-
message: 'Role (e.g. Sales assistant, Tech support):',
|
|
348
|
-
default: 'Virtual assistant',
|
|
349
|
-
},
|
|
350
|
-
{
|
|
351
|
-
type: 'confirm',
|
|
352
|
-
name: 'firstPerson',
|
|
353
|
-
message: 'Speak in first person?',
|
|
354
|
-
default: true,
|
|
355
|
-
},
|
|
365
|
+
{ type: 'input', name: 'name', message: 'Character name (how it introduces itself):', default: basic.name.trim() },
|
|
366
|
+
{ type: 'input', name: 'role', message: 'Role (e.g. Sales assistant, Tech support):', default: 'Virtual assistant' },
|
|
367
|
+
{ type: 'confirm', name: 'firstPerson', message: 'Speak in first person?', default: true },
|
|
356
368
|
]);
|
|
357
|
-
//
|
|
369
|
+
// Step 4: fallbacks (end-user facing, defaults follow the agent language)
|
|
358
370
|
console.log(theme.bold('\n Step 4/8: Fallback messages'));
|
|
371
|
+
console.log(theme.muted(' Sent to your customers, in the agent language.\n'));
|
|
372
|
+
const fb = FALLBACK_DEFAULTS[personality.language] || FALLBACK_DEFAULTS.Espanol;
|
|
359
373
|
const fallbacks = await inquirer.prompt([
|
|
360
|
-
{
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
message: 'When it has no answer:',
|
|
364
|
-
default: 'Lo siento, no tengo informacion sobre eso. Puedo ayudarte con otra consulta?',
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
type: 'input',
|
|
368
|
-
name: 'serviceError',
|
|
369
|
-
message: 'When there is a service error:',
|
|
370
|
-
default: 'Disculpa, estamos experimentando dificultades tecnicas. Intenta de nuevo en unos momentos.',
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
type: 'input',
|
|
374
|
-
name: 'misunderstanding',
|
|
375
|
-
message: 'When it does not understand the question:',
|
|
376
|
-
default: 'No estoy seguro de entender tu consulta. Podrias reformularla?',
|
|
377
|
-
},
|
|
374
|
+
{ type: 'input', name: 'noAnswer', message: 'When it has no answer:', default: fb.noAnswer },
|
|
375
|
+
{ type: 'input', name: 'serviceError', message: 'When an external service fails:', default: fb.serviceError },
|
|
376
|
+
{ type: 'input', name: 'misunderstanding', message: 'When it does not understand the question:', default: fb.doNotUnderstand },
|
|
378
377
|
]);
|
|
379
|
-
//
|
|
378
|
+
// Step 5: Tool Calling
|
|
380
379
|
console.log(theme.bold('\n Step 5/8: Tool Calling'));
|
|
381
|
-
console.log(theme.muted(' Tool Calling lets the agent
|
|
380
|
+
console.log(theme.muted(' Tool Calling lets the agent call your APIs and run actions (tag, stage, hand off...).\n'));
|
|
382
381
|
const { useToolCalling } = await inquirer.prompt([
|
|
383
|
-
{
|
|
384
|
-
type: 'confirm',
|
|
385
|
-
name: 'useToolCalling',
|
|
386
|
-
message: 'Enable Tool Calling?',
|
|
387
|
-
default: true,
|
|
388
|
-
},
|
|
382
|
+
{ type: 'confirm', name: 'useToolCalling', message: 'Enable Tool Calling?', default: true },
|
|
389
383
|
]);
|
|
390
|
-
// Servicios (API calls)
|
|
391
384
|
const services = [];
|
|
385
|
+
const actions = [];
|
|
392
386
|
if (useToolCalling) {
|
|
393
387
|
const { addServices } = await inquirer.prompt([{
|
|
394
|
-
type: 'confirm',
|
|
395
|
-
name: 'addServices',
|
|
396
|
-
message: 'Add external services (API calls)?',
|
|
397
|
-
default: false,
|
|
388
|
+
type: 'confirm', name: 'addServices', message: 'Add services (external API calls)?', default: false,
|
|
398
389
|
}]);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
{ type: 'input', name: 'intent', message: 'Service name/intent:', validate: (v) => v.length > 0 || 'Required' },
|
|
405
|
-
{ type: 'input', name: 'reference', message: 'Reference keywords:', default: '' },
|
|
406
|
-
{ type: 'list', name: 'method', message: 'HTTP method:', choices: ['GET', 'POST'] },
|
|
407
|
-
{ type: 'input', name: 'endpoint', message: 'Endpoint URL:', validate: (v) => v.length > 0 || 'Required' },
|
|
408
|
-
{ type: 'input', name: 'responseMessage', message: 'Response message:', default: '' },
|
|
409
|
-
]);
|
|
410
|
-
// Required fields
|
|
411
|
-
const fields = [];
|
|
412
|
-
const { addFields } = await inquirer.prompt([{
|
|
413
|
-
type: 'confirm', name: 'addFields',
|
|
414
|
-
message: 'Add required fields?', default: false,
|
|
415
|
-
}]);
|
|
416
|
-
if (addFields) {
|
|
417
|
-
let moreFields = true;
|
|
418
|
-
while (moreFields) {
|
|
419
|
-
const field = await inquirer.prompt([
|
|
420
|
-
{ type: 'input', name: 'name', message: 'Field name:' },
|
|
421
|
-
{ type: 'list', name: 'type', message: 'Type:', choices: ['string', 'number', 'boolean', 'date'] },
|
|
422
|
-
{ type: 'input', name: 'description', message: 'Description:' },
|
|
423
|
-
{ type: 'input', name: 'promptHint', message: 'Hint for the LLM:' },
|
|
424
|
-
]);
|
|
425
|
-
fields.push(field);
|
|
426
|
-
const { more } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another field?', default: false }]);
|
|
427
|
-
moreFields = more;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
services.push({
|
|
431
|
-
intent: svc.intent,
|
|
432
|
-
reference: svc.reference,
|
|
433
|
-
enabled: true,
|
|
434
|
-
method: svc.method,
|
|
435
|
-
endpoint: svc.endpoint,
|
|
436
|
-
requiredFields: fields,
|
|
437
|
-
headers: {},
|
|
438
|
-
bodyTemplate: {},
|
|
439
|
-
responseMapping: {},
|
|
440
|
-
responseMessage: svc.responseMessage,
|
|
441
|
-
responseConditions: [],
|
|
442
|
-
});
|
|
443
|
-
const { more } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another service?', default: false }]);
|
|
444
|
-
addMore = more;
|
|
445
|
-
}
|
|
390
|
+
let more = addServices;
|
|
391
|
+
while (more) {
|
|
392
|
+
console.log(theme.muted('\n New service:'));
|
|
393
|
+
services.push(await promptService());
|
|
394
|
+
({ more } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another service?', default: false }]));
|
|
446
395
|
}
|
|
447
|
-
}
|
|
448
|
-
// Acciones
|
|
449
|
-
const actions = [];
|
|
450
|
-
if (useToolCalling) {
|
|
451
396
|
const { addActions } = await inquirer.prompt([{
|
|
452
|
-
type: 'confirm',
|
|
453
|
-
name: 'addActions',
|
|
454
|
-
message: 'Add actions (tag, stage, assign agent)?',
|
|
455
|
-
default: false,
|
|
397
|
+
type: 'confirm', name: 'addActions', message: 'Add actions (tag, stage, assign, hand off to a human...)?', default: false,
|
|
456
398
|
}]);
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
{ type: 'input', name: 'intent', message: 'Action name/intent:', validate: (v) => v.length > 0 || 'Required' },
|
|
463
|
-
{ type: 'input', name: 'reference', message: 'Reference keywords:', default: '' },
|
|
464
|
-
{
|
|
465
|
-
type: 'list', name: 'actionType', message: 'Action type:',
|
|
466
|
-
choices: [
|
|
467
|
-
{ name: 'Schedule event', value: 'action.event.add' },
|
|
468
|
-
{ name: 'Update event (reschedule)', value: 'action.event.update' },
|
|
469
|
-
{ name: 'List events', value: 'action.event.list' },
|
|
470
|
-
{ name: 'Delete event (cancel)', value: 'action.event.delete' },
|
|
471
|
-
{ name: 'Add tag', value: 'action.tag' },
|
|
472
|
-
{ name: 'Change stage', value: 'action.stage' },
|
|
473
|
-
{ name: 'Hand off to human agent', value: 'action.agentShutDown' },
|
|
474
|
-
{ name: 'Mark as resolved', value: 'action.solved' },
|
|
475
|
-
{ name: 'Assign agent', value: 'action.asign' },
|
|
476
|
-
{ name: 'Segmentation', value: 'action.segmentation' },
|
|
477
|
-
],
|
|
478
|
-
},
|
|
479
|
-
{ type: 'input', name: 'actionValue', message: 'Action value:', default: '' },
|
|
480
|
-
{ type: 'input', name: 'responseMessage', message: 'Response message:', default: '' },
|
|
481
|
-
]);
|
|
482
|
-
actions.push({
|
|
483
|
-
intent: act.intent,
|
|
484
|
-
reference: act.reference,
|
|
485
|
-
enabled: true,
|
|
486
|
-
requiredFields: [],
|
|
487
|
-
responseMessage: act.responseMessage,
|
|
488
|
-
action: [{ type: act.actionType, value: act.actionValue }],
|
|
489
|
-
});
|
|
490
|
-
const { more } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another action?', default: false }]);
|
|
491
|
-
addMore = more;
|
|
492
|
-
}
|
|
399
|
+
more = addActions;
|
|
400
|
+
while (more) {
|
|
401
|
+
console.log(theme.muted('\n New action:'));
|
|
402
|
+
actions.push(await promptAction());
|
|
403
|
+
({ more } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another action?', default: false }]));
|
|
493
404
|
}
|
|
494
405
|
}
|
|
495
|
-
//
|
|
406
|
+
// Step 6: AI provider
|
|
496
407
|
console.log(theme.bold('\n Step 6/8: AI provider'));
|
|
497
408
|
const { configureAI } = await inquirer.prompt([{
|
|
498
409
|
type: 'confirm',
|
|
499
410
|
name: 'configureAI',
|
|
500
|
-
message: '
|
|
411
|
+
message: 'Use your own AI provider and API key? (No = platform default AI)',
|
|
501
412
|
default: false,
|
|
502
413
|
}]);
|
|
503
414
|
const aiProviders = [];
|
|
504
415
|
if (configureAI) {
|
|
505
|
-
const
|
|
506
|
-
{ type: 'list', name: 'provider', message: 'Provider:', choices:
|
|
416
|
+
const { provider } = await inquirer.prompt([
|
|
417
|
+
{ type: 'list', name: 'provider', message: 'Provider:', choices: AI_PROVIDERS.map(p => ({ name: p.label, value: p.value })) },
|
|
507
418
|
]);
|
|
508
|
-
const
|
|
419
|
+
const providerId = provider;
|
|
509
420
|
const aiConfig = await inquirer.prompt([
|
|
510
|
-
{ type: 'list', name: 'model', message: 'Model:', choices:
|
|
511
|
-
{ type: 'password', name: 'apiToken', message: 'API
|
|
421
|
+
{ type: 'list', name: 'model', message: 'Model:', choices: AI_MODELS[providerId], default: DEFAULT_AI_MODEL[providerId], pageSize: 12 },
|
|
422
|
+
{ type: 'password', name: 'apiToken', message: 'API token:', mask: '*', validate: (v) => v.trim().length > 0 || 'The API token is required' },
|
|
512
423
|
]);
|
|
513
424
|
aiProviders.push({
|
|
514
|
-
provider:
|
|
425
|
+
provider: providerId,
|
|
515
426
|
model: aiConfig.model,
|
|
516
|
-
apiToken: aiConfig.apiToken,
|
|
517
|
-
temperature:
|
|
518
|
-
maxTokens:
|
|
427
|
+
apiToken: aiConfig.apiToken.trim(),
|
|
428
|
+
temperature: DEFAULT_TEMPERATURE,
|
|
429
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
519
430
|
isDefault: true,
|
|
520
431
|
});
|
|
432
|
+
console.log(theme.muted(` Temperature ${DEFAULT_TEMPERATURE}, max tokens ${DEFAULT_MAX_TOKENS}. Tune them later with: plazbot agent ai-config <agentId>`));
|
|
521
433
|
}
|
|
522
|
-
//
|
|
523
|
-
console.log(theme.bold('\n Step 7/8: Connect channel'));
|
|
434
|
+
// Step 7: channels
|
|
435
|
+
console.log(theme.bold('\n Step 7/8: Connect a channel'));
|
|
524
436
|
let channels = [];
|
|
525
437
|
const { addChannel } = await inquirer.prompt([{
|
|
526
438
|
type: 'confirm',
|
|
527
439
|
name: 'addChannel',
|
|
528
|
-
message: 'Connect a channel (WhatsApp, Instagram, Messenger)?',
|
|
440
|
+
message: 'Connect a channel now (WhatsApp, Instagram, Messenger, Telegram)?',
|
|
529
441
|
default: false,
|
|
530
442
|
}]);
|
|
531
443
|
if (addChannel && workspaceId && apiKey) {
|
|
532
444
|
channels = await connectChannelFlow({ zone, workspaceId, apiKey, dev: dev || false });
|
|
533
445
|
}
|
|
534
446
|
else if (addChannel) {
|
|
535
|
-
//
|
|
447
|
+
// Manual fallback when there are no credentials to call the API
|
|
536
448
|
const wa = await inquirer.prompt([
|
|
537
|
-
{ type: 'input', name: '
|
|
538
|
-
{ type: 'confirm', name: 'multianswer', message: 'Allow multiple
|
|
449
|
+
{ type: 'input', name: 'phone', message: 'WhatsApp number with country code (e.g. +51912345678):', validate: validatePhone },
|
|
450
|
+
{ type: 'confirm', name: 'multianswer', message: 'Allow multiple replies per message?', default: false },
|
|
539
451
|
]);
|
|
540
|
-
channels.push({ channel: 'whatsapp', key: wa.
|
|
452
|
+
channels.push({ channel: 'whatsapp', key: normalizePhone(wa.phone), multianswer: wa.multianswer });
|
|
541
453
|
}
|
|
542
|
-
//
|
|
454
|
+
// Step 8: examples
|
|
543
455
|
console.log(theme.bold('\n Step 8/8: Conversation examples'));
|
|
544
456
|
const examples = [];
|
|
545
457
|
const { addExamples } = await inquirer.prompt([{
|
|
546
458
|
type: 'confirm',
|
|
547
459
|
name: 'addExamples',
|
|
548
|
-
message: 'Add
|
|
460
|
+
message: 'Add example conversations to guide the agent?',
|
|
549
461
|
default: false,
|
|
550
462
|
}]);
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const { more } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another example?', default: false }]);
|
|
560
|
-
addMore = more;
|
|
561
|
-
}
|
|
463
|
+
let moreExamples = addExamples;
|
|
464
|
+
while (moreExamples) {
|
|
465
|
+
const ex = await inquirer.prompt([
|
|
466
|
+
{ type: 'input', name: 'user', message: 'Customer message:', validate: (v) => v.trim().length > 0 || 'Required' },
|
|
467
|
+
{ type: 'input', name: 'agent', message: 'Agent reply:', validate: (v) => v.trim().length > 0 || 'Required' },
|
|
468
|
+
]);
|
|
469
|
+
examples.push({ value: `User: ${ex.user}\nAgent: ${ex.agent}`, color: 'blue' });
|
|
470
|
+
({ more: moreExamples } = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another example?', default: false }]));
|
|
562
471
|
}
|
|
563
|
-
//
|
|
472
|
+
// Final config (aligned with the backend agent.config.schema.json)
|
|
564
473
|
const config = {
|
|
565
|
-
name: basic.name,
|
|
474
|
+
name: basic.name.trim(),
|
|
566
475
|
description: basic.description || '',
|
|
567
476
|
prompt: basic.prompt,
|
|
568
|
-
buffer: basic.buffer,
|
|
477
|
+
buffer: Number(basic.buffer),
|
|
569
478
|
zone,
|
|
570
479
|
color: basic.color,
|
|
571
480
|
useToolCalling,
|
|
@@ -598,18 +507,20 @@ export async function runAgentWizard(zone, workspaceId, apiKey, dev) {
|
|
|
598
507
|
// Preview
|
|
599
508
|
console.log(section('Configuration preview'));
|
|
600
509
|
console.log(kvPair('Name', config.name));
|
|
601
|
-
console.log(kvPair('Prompt', config.prompt.substring(0, 80) + '
|
|
510
|
+
console.log(kvPair('Prompt', config.prompt.length > 80 ? config.prompt.substring(0, 80) + '…' : config.prompt));
|
|
511
|
+
console.log(kvPair('Memory', `${config.buffer} messages`));
|
|
512
|
+
console.log(kvPair('Language', LANGUAGES.find(l => l.value === config.instructions.language)?.name || config.instructions.language));
|
|
602
513
|
console.log(kvPair('Tool Calling', config.useToolCalling ? 'Enabled' : 'Disabled'));
|
|
603
514
|
console.log(kvPair('Services', String(config.services.length)));
|
|
604
515
|
console.log(kvPair('Actions', String(config.actions.length)));
|
|
605
|
-
console.log(kvPair('AI
|
|
516
|
+
console.log(kvPair('AI provider', config.customAIConfig ? `${config.aiProviders[0]?.provider} / ${config.aiProviders[0]?.model}` : 'Platform default (Plazbot)'));
|
|
606
517
|
if (config.channels.length > 0) {
|
|
607
518
|
config.channels.forEach((ch, i) => {
|
|
608
519
|
console.log(kvPair(`Channel ${i + 1} (${ch.channel})`, ch.key));
|
|
609
520
|
});
|
|
610
521
|
}
|
|
611
522
|
else {
|
|
612
|
-
console.log(kvPair('Channels', 'Not
|
|
523
|
+
console.log(kvPair('Channels', 'Not connected'));
|
|
613
524
|
}
|
|
614
525
|
console.log();
|
|
615
526
|
return config;
|