plazbot-cli 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +67 -111
  2. package/dist/cli.js +26 -11
  3. package/dist/commands/agent/ai-config.js +264 -63
  4. package/dist/commands/agent/chat.js +16 -11
  5. package/dist/commands/agent/copy.js +49 -13
  6. package/dist/commands/agent/create.js +62 -17
  7. package/dist/commands/agent/delete.js +50 -39
  8. package/dist/commands/agent/enable-widget.js +47 -27
  9. package/dist/commands/agent/export.js +179 -88
  10. package/dist/commands/agent/files.js +155 -73
  11. package/dist/commands/agent/get.js +24 -16
  12. package/dist/commands/agent/index.js +12 -10
  13. package/dist/commands/agent/list.js +20 -11
  14. package/dist/commands/agent/monitor.js +101 -53
  15. package/dist/commands/agent/on-message.js +55 -32
  16. package/dist/commands/agent/set.js +230 -82
  17. package/dist/commands/agent/templates.js +259 -156
  18. package/dist/commands/agent/tools.js +356 -123
  19. package/dist/commands/agent/update.js +69 -28
  20. package/dist/commands/agent/validate.js +72 -40
  21. package/dist/commands/agent/wizard.js +240 -329
  22. package/dist/commands/auth/browser-login.js +37 -31
  23. package/dist/commands/auth/login.js +49 -45
  24. package/dist/commands/auth/logout.js +20 -11
  25. package/dist/commands/auth/status.js +36 -12
  26. package/dist/commands/auth/workspace.js +61 -27
  27. package/dist/commands/portal/add-agent.js +106 -45
  28. package/dist/commands/portal/add-link.js +25 -28
  29. package/dist/commands/portal/clear-links.js +39 -19
  30. package/dist/commands/portal/create.js +66 -37
  31. package/dist/commands/portal/delete.js +40 -45
  32. package/dist/commands/portal/get.js +21 -54
  33. package/dist/commands/portal/index.js +12 -12
  34. package/dist/commands/portal/list.js +34 -52
  35. package/dist/commands/portal/shared.js +92 -0
  36. package/dist/commands/portal/update.js +100 -70
  37. package/dist/commands/whatsapp/broadcast.js +169 -77
  38. package/dist/commands/whatsapp/channels.js +167 -65
  39. package/dist/commands/whatsapp/chat.js +45 -46
  40. package/dist/commands/whatsapp/connect-flow.js +16 -15
  41. package/dist/commands/whatsapp/connect.js +36 -21
  42. package/dist/commands/whatsapp/delete-webhook.js +42 -25
  43. package/dist/commands/whatsapp/index.js +15 -16
  44. package/dist/commands/whatsapp/register-webhook.js +43 -26
  45. package/dist/commands/whatsapp/send-template.js +62 -61
  46. package/dist/commands/whatsapp/send.js +46 -33
  47. package/dist/commands/whatsapp/setup.js +62 -52
  48. package/dist/commands/whatsapp/shared.js +75 -0
  49. package/dist/commands/whatsapp/widget.js +82 -45
  50. package/dist/commands/workers/deploy.js +80 -58
  51. package/dist/commands/workers/index.js +8 -8
  52. package/dist/commands/workers/list.js +42 -20
  53. package/dist/commands/workers/logs.js +139 -36
  54. package/dist/commands/workers/remove.js +45 -47
  55. package/dist/commands/workers/secret.js +159 -83
  56. package/dist/commands/workers/test.js +108 -60
  57. package/dist/schemas/agent.config.schema.json +159 -8
  58. package/dist/studio/api/sseClient.js +1 -1
  59. package/dist/studio/api/studioApi.js +8 -0
  60. package/dist/studio/components/App.js +12 -0
  61. package/dist/studio/components/Footer.js +1 -1
  62. package/dist/studio/index.js +77 -14
  63. package/dist/studio/runOneShot.js +62 -23
  64. package/dist/studio/runRepl.js +5 -5
  65. package/dist/studio/slash/handlers.js +11 -8
  66. package/dist/studio/state/store.js +1 -1
  67. package/dist/utils/agent-errors.js +14 -11
  68. package/dist/utils/agent-patch.js +60 -0
  69. package/dist/utils/banner.js +9 -5
  70. package/dist/utils/confirm.js +31 -0
  71. package/dist/utils/credentials.js +13 -3
  72. package/dist/utils/logger.js +13 -10
  73. package/dist/utils/output.js +78 -0
  74. package/dist/utils/phone.js +22 -0
  75. package/dist/utils/ui.js +14 -11
  76. package/package.json +2 -2
@@ -3,130 +3,278 @@ import { Agent } from 'plazbot';
3
3
  import inquirer from 'inquirer';
4
4
  import { getStoredCredentials } from '../../utils/credentials.js';
5
5
  import { logger } from '../../utils/logger.js';
6
- import { addExamples } from '../../utils/help.js';
6
+ import { addExamples, addNotes } from '../../utils/help.js';
7
7
  import { createSpinner, section } from '../../utils/ui.js';
8
+ import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
9
+ import { patchAgent } from '../../utils/agent-patch.js';
10
+ // Valores que guarda la plataforma (se mantienen tal cual) con su etiqueta en ingles.
11
+ // Los flags aceptan tambien valores libres.
12
+ const TONES = [
13
+ { name: 'Professional', value: 'profesional' },
14
+ { name: 'Friendly', value: 'amigable' },
15
+ { name: 'Formal', value: 'formal' },
16
+ { name: 'Casual', value: 'casual' },
17
+ { name: 'Technical', value: 'tecnico' },
18
+ { name: 'Empathetic', value: 'empatico' },
19
+ ];
20
+ const STYLES = [
21
+ { name: 'Concise', value: 'conciso' },
22
+ { name: 'Detailed', value: 'detallado' },
23
+ { name: 'Conversational', value: 'conversacional' },
24
+ { name: 'Direct', value: 'directo' },
25
+ ];
26
+ const LANGUAGES = [
27
+ { name: 'Spanish', value: 'Espanol' },
28
+ { name: 'English', value: 'English' },
29
+ { name: 'Portuguese', value: 'Portugues' },
30
+ { name: 'French', value: 'Frances' },
31
+ ];
32
+ /** Choices for a list prompt, keeping a custom current value selectable. */
33
+ function withCurrent(choices, current) {
34
+ if (current && !choices.some((c) => c.value === current)) {
35
+ return [{ name: `${current} (current)`, value: current }, ...choices];
36
+ }
37
+ return choices;
38
+ }
39
+ /** Parses true/false style flag values. */
40
+ function parseBool(value, flag) {
41
+ if (value === undefined)
42
+ return undefined;
43
+ const v = value.trim().toLowerCase();
44
+ if (['true', 'yes', 'y', '1', 'on'].includes(v))
45
+ return true;
46
+ if (['false', 'no', 'n', '0', 'off'].includes(v))
47
+ return false;
48
+ throw new CliError(`Invalid value "${value}" for ${flag}.`, 'invalid_boolean', `Use ${flag} true or ${flag} false.`);
49
+ }
50
+ function canPrompt() {
51
+ return Boolean(process.stdin.isTTY) && !isJsonMode();
52
+ }
53
+ function buildAgent(credentials, dev) {
54
+ return new Agent({
55
+ workspaceId: credentials.workspace,
56
+ apiKey: credentials.apiKey,
57
+ zone: credentials.zone,
58
+ ...(dev && { customUrl: "http://localhost:5090" })
59
+ });
60
+ }
61
+ async function loadAgent(agent, agentId) {
62
+ const spinner = createSpinner('Loading agent...');
63
+ spinner.start();
64
+ try {
65
+ const res = await agent.getAgentById({ id: agentId });
66
+ const data = res?.agent || res?.data || res;
67
+ if (!data || !data.name) {
68
+ throw new CliError(`Agent ${agentId} was not found in the active workspace.`, 'not_found', "Check the ID with 'plazbot agent list'.");
69
+ }
70
+ return data;
71
+ }
72
+ finally {
73
+ spinner.stop();
74
+ }
75
+ }
76
+ /**
77
+ * Guarda cambios parciales del agente. Los endpoints set-greeting/set-instructions/set-persona
78
+ * que usa el SDK no existen en la API, y POST /api/agent/update REEMPLAZA el documento completo:
79
+ * por eso se parte de la config actual (limpia, como en export), se aplica el parche y se envia todo.
80
+ */
81
+ async function saveAgentPatch(credentials, dev, agentId, current, patch) {
82
+ await patchAgent({ apiKey: credentials.apiKey, workspace: credentials.workspace, zone: credentials.zone, dev }, agentId, patch, current);
83
+ }
8
84
  const setGroup = new Command('set')
9
- .description('Quickly configure agent properties');
85
+ .description('Quickly change the greeting, instructions or persona of an agent');
10
86
  addExamples(setGroup, [
11
87
  { description: 'Replace the agent greeting',
12
- command: 'plazbot agent set greeting agt_AbcDef123 "Hi! How can I help you today?"' },
13
- { description: 'Configure tone, style, language, etc. interactively',
14
- command: 'plazbot agent set instructions agt_AbcDef123' },
15
- { description: 'Define the agent persona (name, role, voice)',
16
- command: 'plazbot agent set persona agt_AbcDef123' },
88
+ command: 'plazbot agent set greeting age_AbcDef123 "Hi! How can I help you today?"' },
89
+ { description: 'Change tone and language without prompts',
90
+ command: 'plazbot agent set instructions age_AbcDef123 --tone amigable --language English' },
91
+ { description: 'Define the agent persona',
92
+ command: 'plazbot agent set persona age_AbcDef123 --name "Sofia" --role "Sales advisor" --human false' },
17
93
  ]);
18
- // Set greeting
19
- setGroup.command('greeting')
20
- .description('Change the agent greeting')
94
+ // ── greeting ─────────────────────────────────────────────────────────
95
+ const greetingCmd = setGroup.command('greeting')
96
+ .description('Change the agent greeting (first message of a conversation)')
21
97
  .argument('<agentId>', 'Agent ID')
22
- .argument('<greeting>', 'New greeting')
98
+ .argument('<greeting>', 'New greeting text (quote it)')
99
+ .addOption(jsonOption('Print the result as JSON ({"id","updated","greeting"})'))
23
100
  .option('--dev', 'Use development environment', false)
24
101
  .action(async (agentId, greeting, options) => {
25
102
  try {
103
+ if (!greeting || !greeting.trim()) {
104
+ throw new CliError('The greeting cannot be empty.', 'missing_greeting');
105
+ }
26
106
  const credentials = await getStoredCredentials();
27
- const agent = new Agent({
28
- workspaceId: credentials.workspace,
29
- apiKey: credentials.apiKey,
30
- zone: credentials.zone,
31
- ...(options.dev && { customUrl: "http://localhost:5090" })
32
- });
107
+ const agent = buildAgent(credentials, options.dev);
108
+ const current = await loadAgent(agent, agentId);
33
109
  const spinner = createSpinner('Updating greeting...');
34
110
  spinner.start();
35
- await agent.setGreeting(agentId, greeting);
111
+ try {
112
+ await saveAgentPatch(credentials, options.dev, agentId, current, {
113
+ instructions: { ...(current.instructions || {}), greeting },
114
+ });
115
+ }
116
+ catch (err) {
117
+ spinner.fail('Could not update the greeting');
118
+ throw err;
119
+ }
36
120
  spinner.succeed('Greeting updated');
121
+ if (isJsonMode()) {
122
+ printJson({ id: agentId, updated: true, greeting });
123
+ return;
124
+ }
37
125
  logger.label('New greeting', greeting);
38
126
  }
39
127
  catch (error) {
40
- const message = error instanceof Error ? error.message : 'Unknown error';
41
- logger.error(message);
42
- process.exit(1);
128
+ fail(error);
43
129
  }
44
130
  });
45
- // Set instructions (wizard)
46
- setGroup.command('instructions')
47
- .description('Configure agent instructions')
131
+ addExamples(greetingCmd, [
132
+ { description: 'Set a new greeting',
133
+ command: 'plazbot agent set greeting age_AbcDef123 "Hi! How can I help you today?"' },
134
+ ]);
135
+ const instructionsCmd = setGroup.command('instructions')
136
+ .description('Configure tone, style, personality, objective, language and emojis')
48
137
  .argument('<agentId>', 'Agent ID')
138
+ .option('--tone <tone>', 'Tone: profesional, amigable, formal, casual, tecnico, empatico, or any custom value')
139
+ .option('--style <style>', 'Style: conciso, detallado, conversacional, directo, or any custom value')
140
+ .option('--personality <text>', 'Personality in one sentence')
141
+ .option('--objective <text>', 'Main objective of the agent')
142
+ .option('--language <language>', 'Language: Espanol, English, Portugues, Frances, or any custom value')
143
+ .option('--emojis <true|false>', 'Use emojis in answers')
144
+ .addOption(jsonOption('Print the result as JSON ({"id","updated","instructions"})'))
49
145
  .option('--dev', 'Use development environment', false)
50
146
  .action(async (agentId, options) => {
51
147
  try {
148
+ const emojisFlag = parseBool(options.emojis, '--emojis');
149
+ const flags = {
150
+ tone: options.tone,
151
+ style: options.style,
152
+ personality: options.personality,
153
+ objective: options.objective,
154
+ language: options.language,
155
+ emojis: emojisFlag,
156
+ };
157
+ const given = Object.fromEntries(Object.entries(flags).filter(([, v]) => v !== undefined));
158
+ const hasFlags = Object.keys(given).length > 0;
159
+ if (!hasFlags && !canPrompt()) {
160
+ throw new CliError('No instruction values were given and there is no interactive terminal.', 'missing_values', `Pass at least one flag, e.g. plazbot agent set instructions ${agentId} --tone amigable --emojis false`);
161
+ }
52
162
  const credentials = await getStoredCredentials();
53
- const agent = new Agent({
54
- workspaceId: credentials.workspace,
55
- apiKey: credentials.apiKey,
56
- zone: credentials.zone,
57
- ...(options.dev && { customUrl: "http://localhost:5090" })
58
- });
59
- // Cargar instrucciones actuales
60
- const loadSpinner = createSpinner('Loading...');
61
- loadSpinner.start();
62
- const _res1 = await agent.getAgentById({ id: agentId });
63
- const agentData = _res1.agent || _res1.data || _res1;
64
- loadSpinner.stop();
163
+ const agent = buildAgent(credentials, options.dev);
164
+ const agentData = await loadAgent(agent, agentId);
65
165
  const current = agentData.instructions || {};
66
- console.log(section('Instructions - ' + (agentData.name || agentId)));
67
- const answers = await inquirer.prompt([
68
- { type: 'list', name: 'tone', message: 'Tone:', choices: ['profesional', 'amigable', 'formal', 'casual', 'tecnico', 'empatico'], default: current.tone || 'profesional' },
69
- { type: 'list', name: 'style', message: 'Style:', choices: ['conciso', 'detallado', 'conversacional', 'directo'], default: current.style || 'conciso' },
70
- { type: 'input', name: 'personality', message: 'Personality:', default: current.personality || '' },
71
- { type: 'input', name: 'objective', message: 'Objective:', default: current.objective || '' },
72
- { type: 'list', name: 'language', message: 'Language:', choices: ['Espanol', 'English', 'Portugues'], default: current.language || 'Espanol' },
73
- { type: 'confirm', name: 'emojis', message: 'Use emojis?', default: current.emojis === true },
74
- ]);
75
- const instructions = {
76
- ...current,
77
- tone: answers.tone,
78
- style: answers.style,
79
- personality: answers.personality,
80
- objective: answers.objective,
81
- language: answers.language,
82
- emojis: answers.emojis,
83
- };
166
+ let changes = given;
167
+ if (!hasFlags) {
168
+ // Sin flags y con terminal: asistente con los valores actuales por defecto
169
+ console.log(section('Instructions - ' + (agentData.name || agentId)));
170
+ changes = await inquirer.prompt([
171
+ { type: 'list', name: 'tone', message: 'Tone:', choices: withCurrent(TONES, current.tone), default: current.tone || 'profesional' },
172
+ { type: 'list', name: 'style', message: 'Style:', choices: withCurrent(STYLES, current.style), default: current.style || 'conciso' },
173
+ { type: 'input', name: 'personality', message: 'Personality:', default: current.personality || '' },
174
+ { type: 'input', name: 'objective', message: 'Objective:', default: current.objective || '' },
175
+ { type: 'list', name: 'language', message: 'Language:', choices: withCurrent(LANGUAGES, current.language), default: current.language || 'Espanol' },
176
+ { type: 'confirm', name: 'emojis', message: 'Use emojis?', default: current.emojis === true },
177
+ ]);
178
+ }
179
+ const instructions = { ...current, ...changes };
84
180
  const spinner = createSpinner('Saving instructions...');
85
181
  spinner.start();
86
- await agent.setInstructions(agentId, instructions);
182
+ try {
183
+ await saveAgentPatch(credentials, options.dev, agentId, agentData, { instructions });
184
+ }
185
+ catch (err) {
186
+ spinner.fail('Could not save the instructions');
187
+ throw err;
188
+ }
87
189
  spinner.succeed('Instructions updated');
190
+ if (isJsonMode()) {
191
+ printJson({ id: agentId, updated: true, fields: Object.keys(changes), instructions });
192
+ return;
193
+ }
194
+ Object.entries(changes).forEach(([key, value]) => logger.label(key, String(value)));
88
195
  }
89
196
  catch (error) {
90
- const message = error instanceof Error ? error.message : 'Unknown error';
91
- logger.error(message);
92
- process.exit(1);
197
+ fail(error);
93
198
  }
94
199
  });
95
- // Set persona (wizard)
96
- setGroup.command('persona')
97
- .description('Configure the agent persona')
200
+ addExamples(instructionsCmd, [
201
+ { description: 'Edit every value interactively',
202
+ command: 'plazbot agent set instructions age_AbcDef123' },
203
+ { description: 'Change only tone and emojis (no prompts)',
204
+ command: 'plazbot agent set instructions age_AbcDef123 --tone amigable --emojis false' },
205
+ { description: 'Set a custom objective and language',
206
+ command: 'plazbot agent set instructions age_AbcDef123 --objective "Book demos" --language English' },
207
+ ]);
208
+ addNotes(instructionsCmd, [
209
+ 'With flags, only the given values change and nothing is asked. Without flags, an interactive editor opens.',
210
+ ]);
211
+ const personaCmd = setGroup.command('persona')
212
+ .description('Configure the persona of the agent (name, role, voice)')
98
213
  .argument('<agentId>', 'Agent ID')
214
+ .option('--name <name>', 'Character name, e.g. "Sofia"')
215
+ .option('--role <role>', 'Role, e.g. "Sales advisor"')
216
+ .option('--first-person <true|false>', 'Speak in first person')
217
+ .option('--human <true|false>', 'Present itself as a human instead of a virtual assistant')
218
+ .addOption(jsonOption('Print the result as JSON ({"id","updated","persona"})'))
99
219
  .option('--dev', 'Use development environment', false)
100
220
  .action(async (agentId, options) => {
101
221
  try {
222
+ const flags = {
223
+ name: options.name,
224
+ role: options.role,
225
+ speaksInFirstPerson: parseBool(options.firstPerson, '--first-person'),
226
+ isHuman: parseBool(options.human, '--human'),
227
+ };
228
+ const given = Object.fromEntries(Object.entries(flags).filter(([, v]) => v !== undefined));
229
+ const hasFlags = Object.keys(given).length > 0;
230
+ if (!hasFlags && !canPrompt()) {
231
+ throw new CliError('No persona values were given and there is no interactive terminal.', 'missing_values', `Pass at least one flag, e.g. plazbot agent set persona ${agentId} --name "Sofia" --role "Sales advisor"`);
232
+ }
102
233
  const credentials = await getStoredCredentials();
103
- const agent = new Agent({
104
- workspaceId: credentials.workspace,
105
- apiKey: credentials.apiKey,
106
- zone: credentials.zone,
107
- ...(options.dev && { customUrl: "http://localhost:5090" })
108
- });
109
- const loadSpinner = createSpinner('Loading...');
110
- loadSpinner.start();
111
- const _res2 = await agent.getAgentById({ id: agentId });
112
- const agentData = _res2.agent || _res2.data || _res2;
113
- loadSpinner.stop();
234
+ const agent = buildAgent(credentials, options.dev);
235
+ const agentData = await loadAgent(agent, agentId);
114
236
  const current = agentData.person || {};
115
- console.log(section('Persona - ' + (agentData.name || agentId)));
116
- const answers = await inquirer.prompt([
117
- { type: 'input', name: 'name', message: 'Character name:', default: current.name || '' },
118
- { type: 'input', name: 'role', message: 'Role:', default: current.role || '' },
119
- { type: 'confirm', name: 'speaksInFirstPerson', message: 'Speak in first person?', default: current.speaksInFirstPerson !== false },
120
- ]);
237
+ let changes = given;
238
+ if (!hasFlags) {
239
+ console.log(section('Persona - ' + (agentData.name || agentId)));
240
+ changes = await inquirer.prompt([
241
+ { type: 'input', name: 'name', message: 'Character name:', default: current.name || '' },
242
+ { type: 'input', name: 'role', message: 'Role:', default: current.role || '' },
243
+ { type: 'confirm', name: 'speaksInFirstPerson', message: 'Speak in first person?', default: current.speaksInFirstPerson !== false },
244
+ { type: 'confirm', name: 'isHuman', message: 'Present itself as a human?', default: current.isHuman === true },
245
+ ]);
246
+ }
247
+ // Se parte de la persona actual para no perder campos que no se tocan
248
+ const persona = { ...current, ...changes };
121
249
  const spinner = createSpinner('Saving persona...');
122
250
  spinner.start();
123
- await agent.setPersona(agentId, answers);
251
+ try {
252
+ await saveAgentPatch(credentials, options.dev, agentId, agentData, { person: persona });
253
+ }
254
+ catch (err) {
255
+ spinner.fail('Could not save the persona');
256
+ throw err;
257
+ }
124
258
  spinner.succeed('Persona updated');
259
+ if (isJsonMode()) {
260
+ printJson({ id: agentId, updated: true, fields: Object.keys(changes), persona });
261
+ return;
262
+ }
263
+ Object.entries(changes).forEach(([key, value]) => logger.label(key, String(value)));
125
264
  }
126
265
  catch (error) {
127
- const message = error instanceof Error ? error.message : 'Unknown error';
128
- logger.error(message);
129
- process.exit(1);
266
+ fail(error);
130
267
  }
131
268
  });
269
+ addExamples(personaCmd, [
270
+ { description: 'Edit the persona interactively',
271
+ command: 'plazbot agent set persona age_AbcDef123' },
272
+ { description: 'Set name and role (no prompts)',
273
+ command: 'plazbot agent set persona age_AbcDef123 --name "Sofia" --role "Sales advisor"' },
274
+ { description: 'Make the agent speak as a human in first person',
275
+ command: 'plazbot agent set persona age_AbcDef123 --first-person true --human true' },
276
+ ]);
277
+ addNotes(personaCmd, [
278
+ 'With flags, only the given values change and nothing is asked. Without flags, an interactive editor opens.',
279
+ ]);
132
280
  export const setCommand = setGroup;