plazbot-cli 0.4.0 → 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.
- 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 +179 -88
- package/dist/commands/agent/files.js +155 -73
- package/dist/commands/agent/get.js +24 -16
- package/dist/commands/agent/index.js +12 -10
- package/dist/commands/agent/list.js +20 -11
- package/dist/commands/agent/monitor.js +101 -53
- 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 +183 -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 +77 -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 +2 -2
|
@@ -3,8 +3,10 @@ import inquirer from 'inquirer';
|
|
|
3
3
|
import { Agent } from 'plazbot';
|
|
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 { theme, section, kvPair, createTable, createSpinner } from '../../utils/ui.js';
|
|
8
|
+
import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
|
|
9
|
+
import { addYesOption, confirmAction } from '../../utils/confirm.js';
|
|
8
10
|
import { connectChannelFlow } from './wizard.js';
|
|
9
11
|
// Plantilla: Clinica
|
|
10
12
|
const plantillaClinica = {
|
|
@@ -572,60 +574,70 @@ const plantillaVentas = {
|
|
|
572
574
|
"customAIConfig": true,
|
|
573
575
|
"aiProviders": [{ "provider": "openai", "model": "gpt-4o", "apiToken": "", "temperature": 0.7, "maxTokens": 500, "isDefault": true }]
|
|
574
576
|
};
|
|
575
|
-
|
|
577
|
+
const FIELD_FLAGS = {
|
|
578
|
+
business: '--business <name>',
|
|
579
|
+
address: '--address <text>',
|
|
580
|
+
contactPhone: '--contact-phone <number>',
|
|
581
|
+
supportEmail: '--support-email <email>',
|
|
582
|
+
};
|
|
576
583
|
const TEMPLATES = [
|
|
577
584
|
{
|
|
585
|
+
id: 'sales',
|
|
578
586
|
name: 'Sales Assistant',
|
|
579
|
-
description: '
|
|
587
|
+
description: 'Sales management, quotes and orders',
|
|
580
588
|
industry: 'Sales',
|
|
581
589
|
placeholders: [
|
|
582
|
-
{ key: '[NOMBRE DE LA EMPRESA]', label: '
|
|
583
|
-
{ key: '[DIRECCION COMPLETA]', label: '
|
|
584
|
-
{ key: '[NUMERO DE TELEFONO]', label: 'Contact phone' },
|
|
590
|
+
{ key: '[NOMBRE DE LA EMPRESA]', label: 'Company name', field: 'business' },
|
|
591
|
+
{ key: '[DIRECCION COMPLETA]', label: 'Company address', field: 'address' },
|
|
592
|
+
{ key: '[NUMERO DE TELEFONO]', label: 'Contact phone', field: 'contactPhone' },
|
|
585
593
|
],
|
|
586
594
|
config: plantillaVentas,
|
|
587
595
|
},
|
|
588
596
|
{
|
|
597
|
+
id: 'it-support',
|
|
589
598
|
name: 'IT Technical Support',
|
|
590
|
-
description: '
|
|
599
|
+
description: 'Technical support, tickets and troubleshooting',
|
|
591
600
|
industry: 'Technology',
|
|
592
601
|
placeholders: [
|
|
593
|
-
{ key: '[NOMBRE DE LA EMPRESA]', label: '
|
|
594
|
-
{ key: '[NUMERO DE TELEFONO]', label: 'Support phone' },
|
|
595
|
-
{ key: '[EMAIL DE SOPORTE]', label: 'Support email (e.g. support@company.com)' },
|
|
602
|
+
{ key: '[NOMBRE DE LA EMPRESA]', label: 'Company name', field: 'business' },
|
|
603
|
+
{ key: '[NUMERO DE TELEFONO]', label: 'Support phone', field: 'contactPhone' },
|
|
604
|
+
{ key: '[EMAIL DE SOPORTE]', label: 'Support email (e.g. support@company.com)', field: 'supportEmail' },
|
|
596
605
|
],
|
|
597
606
|
config: plantillaSoporteIT,
|
|
598
607
|
},
|
|
599
608
|
{
|
|
609
|
+
id: 'clinic',
|
|
600
610
|
name: 'Clinic Assistant',
|
|
601
|
-
description: '
|
|
611
|
+
description: 'Medical appointments, schedules and specialties',
|
|
602
612
|
industry: 'Health',
|
|
603
613
|
placeholders: [
|
|
604
|
-
{ key: '[NOMBRE DE LA CLINICA]', label: '
|
|
605
|
-
{ key: '[DIRECCION COMPLETA]', label: 'Clinic address' },
|
|
606
|
-
{ key: '[NUMERO DE TELEFONO]', label: 'Clinic phone' },
|
|
614
|
+
{ key: '[NOMBRE DE LA CLINICA]', label: 'Clinic name', field: 'business' },
|
|
615
|
+
{ key: '[DIRECCION COMPLETA]', label: 'Clinic address', field: 'address' },
|
|
616
|
+
{ key: '[NUMERO DE TELEFONO]', label: 'Clinic phone', field: 'contactPhone' },
|
|
607
617
|
],
|
|
608
618
|
config: plantillaClinica,
|
|
609
619
|
},
|
|
610
620
|
{
|
|
621
|
+
id: 'restaurant',
|
|
611
622
|
name: 'Restaurant Assistant',
|
|
612
|
-
description: '
|
|
613
|
-
industry: '
|
|
623
|
+
description: 'Reservations, menu and delivery',
|
|
624
|
+
industry: 'Food & beverage',
|
|
614
625
|
placeholders: [
|
|
615
|
-
{ key: '[NOMBRE DEL RESTAURANTE]', label: '
|
|
616
|
-
{ key: '[DIRECCION COMPLETA]', label: 'Restaurant address' },
|
|
617
|
-
{ key: '[NUMERO DE TELEFONO]', label: 'Restaurant phone' },
|
|
626
|
+
{ key: '[NOMBRE DEL RESTAURANTE]', label: 'Restaurant name', field: 'business' },
|
|
627
|
+
{ key: '[DIRECCION COMPLETA]', label: 'Restaurant address', field: 'address' },
|
|
628
|
+
{ key: '[NUMERO DE TELEFONO]', label: 'Restaurant phone', field: 'contactPhone' },
|
|
618
629
|
],
|
|
619
630
|
config: plantillaRestaurante,
|
|
620
631
|
},
|
|
621
632
|
{
|
|
633
|
+
id: 'real-estate',
|
|
622
634
|
name: 'Real Estate Assistant',
|
|
623
|
-
description: '
|
|
624
|
-
industry: 'Real
|
|
635
|
+
description: 'Properties, viewings and advisory',
|
|
636
|
+
industry: 'Real estate',
|
|
625
637
|
placeholders: [
|
|
626
|
-
{ key: '[NOMBRE DE LA INMOBILIARIA]', label: '
|
|
627
|
-
{ key: '[DIRECCION COMPLETA]', label: 'Office address' },
|
|
628
|
-
{ key: '[NUMERO DE TELEFONO]', label: 'Contact phone' },
|
|
638
|
+
{ key: '[NOMBRE DE LA INMOBILIARIA]', label: 'Agency name', field: 'business' },
|
|
639
|
+
{ key: '[DIRECCION COMPLETA]', label: 'Office address', field: 'address' },
|
|
640
|
+
{ key: '[NUMERO DE TELEFONO]', label: 'Contact phone', field: 'contactPhone' },
|
|
629
641
|
],
|
|
630
642
|
config: plantillaInmobiliario,
|
|
631
643
|
},
|
|
@@ -653,191 +665,282 @@ function replacePlaceholders(obj, replacements) {
|
|
|
653
665
|
}
|
|
654
666
|
return obj;
|
|
655
667
|
}
|
|
668
|
+
/** Accepts a template id ("clinic"), its list number ("3") or its name ("Clinic Assistant"). */
|
|
669
|
+
function findTemplate(value) {
|
|
670
|
+
const v = value.trim().toLowerCase();
|
|
671
|
+
const byIndex = /^\d+$/.test(v) ? TEMPLATES[Number(v) - 1] : undefined;
|
|
672
|
+
const found = byIndex || TEMPLATES.find(t => t.id === v || t.name.toLowerCase() === v);
|
|
673
|
+
if (!found) {
|
|
674
|
+
throw new CliError(`Unknown template "${value}".`, 'invalid_template', `Available: ${TEMPLATES.map(t => t.id).join(', ')}. See them with: plazbot agent templates --list`);
|
|
675
|
+
}
|
|
676
|
+
return found;
|
|
677
|
+
}
|
|
678
|
+
function templateAsJson(t) {
|
|
679
|
+
return {
|
|
680
|
+
id: t.id,
|
|
681
|
+
name: t.name,
|
|
682
|
+
industry: t.industry,
|
|
683
|
+
description: t.description,
|
|
684
|
+
persona: `${t.config.person?.name} - ${t.config.person?.role}`,
|
|
685
|
+
actions: (t.config.actions || []).length,
|
|
686
|
+
fields: t.placeholders.map(p => ({ flag: FIELD_FLAGS[p.field].split(' ')[0], label: p.label })),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function printTemplateTable() {
|
|
690
|
+
console.log(section('Agent templates'));
|
|
691
|
+
console.log(createTable(['#', 'ID', 'Name', 'Industry', 'Description'], TEMPLATES.map((t, i) => [String(i + 1), t.id, t.name, t.industry, t.description])));
|
|
692
|
+
}
|
|
656
693
|
export const templatesCommand = new Command('templates')
|
|
657
|
-
.description('Create an agent from
|
|
658
|
-
.option('--
|
|
659
|
-
.option('--
|
|
660
|
-
.
|
|
694
|
+
.description('Create an agent from a ready-made template (sales, support, clinic, restaurant, real estate)')
|
|
695
|
+
.option('--list', 'List the available templates and exit')
|
|
696
|
+
.option('-t, --template <id>', `Template to use: ${TEMPLATES.map(t => t.id).join(', ')} (or its number)`)
|
|
697
|
+
.option('--name <name>', 'Agent name (default: the template name)')
|
|
698
|
+
.option('--business <name>', 'Business name used in the template prompt')
|
|
699
|
+
.option('--address <text>', 'Business address (templates that use it)')
|
|
700
|
+
.option('--contact-phone <number>', 'Business phone shown to customers (templates that use it)')
|
|
701
|
+
.option('--support-email <email>', 'Support email (it-support template)')
|
|
702
|
+
.option('--timezone <iana>', 'IANA timezone, e.g. America/Lima (default: America/Lima)')
|
|
703
|
+
.option('--api-token <key>', 'OpenAI API key for the agent (templates use OpenAI gpt-4o)')
|
|
704
|
+
.option('--skip-channels', 'Do not offer to connect channels')
|
|
705
|
+
.addOption(jsonOption('Print the resulting agent config as JSON without creating it (no API calls); with --list, print templates as JSON'))
|
|
706
|
+
.option('--dev', 'Use development environment', false);
|
|
707
|
+
addYesOption(templatesCommand);
|
|
708
|
+
templatesCommand.action(async (options) => {
|
|
661
709
|
try {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const { selected } = await inquirer.prompt([{
|
|
673
|
-
type: 'list',
|
|
674
|
-
name: 'selected',
|
|
675
|
-
message: 'Select a template:',
|
|
676
|
-
choices: [
|
|
677
|
-
...TEMPLATES.map((t, i) => ({ name: `${t.name} (${t.industry})`, value: i })),
|
|
678
|
-
{ name: 'Cancel', value: -1 },
|
|
679
|
-
],
|
|
680
|
-
}]);
|
|
681
|
-
if (selected === -1)
|
|
710
|
+
const json = isJsonMode();
|
|
711
|
+
const interactive = !!process.stdin.isTTY && !json;
|
|
712
|
+
// --list
|
|
713
|
+
if (options.list) {
|
|
714
|
+
if (json)
|
|
715
|
+
printJson(TEMPLATES.map(templateAsJson));
|
|
716
|
+
else {
|
|
717
|
+
printTemplateTable();
|
|
718
|
+
console.log(theme.muted('\n Use one with: plazbot agent templates --template <id>\n'));
|
|
719
|
+
}
|
|
682
720
|
return;
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
721
|
+
}
|
|
722
|
+
// 1. Template
|
|
723
|
+
let template;
|
|
724
|
+
if (options.template) {
|
|
725
|
+
template = findTemplate(options.template);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
if (!interactive) {
|
|
729
|
+
throw new CliError('A template is required when running without prompts.', 'missing_template', `Pass it with --template <id> (${TEMPLATES.map(t => t.id).join(', ')}). See plazbot agent templates --list.`);
|
|
730
|
+
}
|
|
731
|
+
printTemplateTable();
|
|
732
|
+
const { selected } = await inquirer.prompt([{
|
|
733
|
+
type: 'list',
|
|
734
|
+
name: 'selected',
|
|
735
|
+
message: 'Select a template:',
|
|
736
|
+
choices: [
|
|
737
|
+
...TEMPLATES.map((t, i) => ({ name: `${t.name} ${theme.muted(`(${t.industry})`)}`, value: i })),
|
|
738
|
+
new inquirer.Separator(),
|
|
739
|
+
{ name: 'Cancel', value: -1 },
|
|
740
|
+
],
|
|
741
|
+
}]);
|
|
742
|
+
if (selected === -1) {
|
|
743
|
+
logger.dim('Aborted.');
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
template = TEMPLATES[selected];
|
|
747
|
+
}
|
|
748
|
+
if (!json) {
|
|
749
|
+
console.log(section(template.name));
|
|
750
|
+
console.log(kvPair('Industry', template.industry));
|
|
751
|
+
console.log(kvPair('Persona', `${template.config.person.name} - ${template.config.person.role}`));
|
|
752
|
+
console.log(kvPair('Actions', String(template.config.actions.length)));
|
|
753
|
+
console.log(kvPair('Tool Calling', 'Enabled'));
|
|
754
|
+
console.log();
|
|
755
|
+
}
|
|
756
|
+
// 2. Placeholders: flags first, then prompts (interactive), else error / keep as is (--json)
|
|
693
757
|
const placeholderAnswers = {};
|
|
758
|
+
const missing = [];
|
|
759
|
+
let headerShown = false;
|
|
694
760
|
for (const ph of template.placeholders) {
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
761
|
+
const fromFlag = (options[ph.field] || '').trim();
|
|
762
|
+
if (fromFlag) {
|
|
763
|
+
placeholderAnswers[ph.key] = fromFlag;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (interactive) {
|
|
767
|
+
if (!headerShown) {
|
|
768
|
+
console.log(theme.bold(' Your business details'));
|
|
769
|
+
headerShown = true;
|
|
770
|
+
}
|
|
771
|
+
const { value } = await inquirer.prompt([{
|
|
772
|
+
type: 'input',
|
|
773
|
+
name: 'value',
|
|
774
|
+
message: `${ph.label}:`,
|
|
775
|
+
validate: (v) => v.trim().length > 0 || 'This field is required',
|
|
776
|
+
}]);
|
|
777
|
+
placeholderAnswers[ph.key] = value.trim();
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
missing.push(ph);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (missing.length > 0) {
|
|
784
|
+
const flags = missing.map(p => FIELD_FLAGS[p.field]).join(', ');
|
|
785
|
+
if (!json) {
|
|
786
|
+
throw new CliError(`Missing business details for the "${template.id}" template.`, 'missing_fields', `Pass ${flags}.`);
|
|
787
|
+
}
|
|
788
|
+
logger.warning(`Placeholders left unfilled: ${missing.map(p => p.key).join(', ')}. Pass ${flags} to fill them.`);
|
|
789
|
+
}
|
|
790
|
+
// 3. Build the config (no API calls)
|
|
791
|
+
const finalConfig = replacePlaceholders(JSON.parse(JSON.stringify(template.config)), placeholderAnswers);
|
|
792
|
+
if (options.name?.trim()) {
|
|
793
|
+
finalConfig.name = options.name.trim();
|
|
794
|
+
}
|
|
795
|
+
finalConfig.channels = [];
|
|
796
|
+
// --json: print the config and stop. Never prompt, never call the API.
|
|
797
|
+
if (json) {
|
|
798
|
+
if (options.timezone)
|
|
799
|
+
finalConfig.timezone = options.timezone.trim();
|
|
800
|
+
if (options.apiToken) {
|
|
801
|
+
logger.warning('The API token is not included in the JSON output. Set it after creating the agent: plazbot agent ai-config <agentId> --api-token <key>');
|
|
802
|
+
}
|
|
803
|
+
printJson(finalConfig);
|
|
804
|
+
return;
|
|
702
805
|
}
|
|
703
|
-
// Paso 3: Canales (WhatsApp, Messenger, Instagram, Telegram)
|
|
704
|
-
console.log(theme.bold('\n Channel connection'));
|
|
705
|
-
const { wantChannels } = await inquirer.prompt([{
|
|
706
|
-
type: 'confirm',
|
|
707
|
-
name: 'wantChannels',
|
|
708
|
-
message: 'Connect channels (WhatsApp, Messenger, Instagram, Telegram)?',
|
|
709
|
-
default: true,
|
|
710
|
-
}]);
|
|
711
806
|
const credentials = await getStoredCredentials();
|
|
807
|
+
// 4. Channels (interactive only)
|
|
712
808
|
let connectedChannels = [];
|
|
713
|
-
if (
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
connectedChannels = await connectChannelFlow(wizardCtx);
|
|
721
|
-
}
|
|
722
|
-
// Paso 4: API Key de OpenAI
|
|
723
|
-
console.log(theme.bold('\n AI configuration'));
|
|
724
|
-
const { configureApiKey } = await inquirer.prompt([{
|
|
725
|
-
type: 'confirm',
|
|
726
|
-
name: 'configureApiKey',
|
|
727
|
-
message: 'Configure your OpenAI API Key now? (required for the agent to work)',
|
|
728
|
-
default: true,
|
|
729
|
-
}]);
|
|
730
|
-
let apiToken = '';
|
|
731
|
-
if (configureApiKey) {
|
|
732
|
-
const { key } = await inquirer.prompt([{
|
|
733
|
-
type: 'password',
|
|
734
|
-
name: 'key',
|
|
735
|
-
message: 'OpenAI API Key (sk-...):',
|
|
736
|
-
mask: '*',
|
|
737
|
-
validate: (v) => v.trim().length > 0 || 'The API Key is required',
|
|
809
|
+
if (interactive && !options.skipChannels) {
|
|
810
|
+
console.log(theme.bold('\n Channels'));
|
|
811
|
+
const { wantChannels } = await inquirer.prompt([{
|
|
812
|
+
type: 'confirm',
|
|
813
|
+
name: 'wantChannels',
|
|
814
|
+
message: 'Connect a channel now (WhatsApp, Messenger, Instagram, Telegram)?',
|
|
815
|
+
default: true,
|
|
738
816
|
}]);
|
|
739
|
-
|
|
817
|
+
if (wantChannels) {
|
|
818
|
+
const wizardCtx = {
|
|
819
|
+
zone: credentials.zone,
|
|
820
|
+
workspaceId: credentials.workspace,
|
|
821
|
+
apiKey: credentials.apiKey,
|
|
822
|
+
dev: !!options.dev,
|
|
823
|
+
};
|
|
824
|
+
connectedChannels = await connectChannelFlow(wizardCtx);
|
|
825
|
+
}
|
|
740
826
|
}
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
827
|
+
// 5. OpenAI API key
|
|
828
|
+
let apiToken = (options.apiToken || '').trim();
|
|
829
|
+
if (!apiToken && interactive) {
|
|
830
|
+
console.log(theme.bold('\n AI configuration'));
|
|
831
|
+
const { configureApiKey } = await inquirer.prompt([{
|
|
832
|
+
type: 'confirm',
|
|
833
|
+
name: 'configureApiKey',
|
|
834
|
+
message: 'Add your OpenAI API key now? (the agent needs it to reply)',
|
|
835
|
+
default: true,
|
|
836
|
+
}]);
|
|
837
|
+
if (configureApiKey) {
|
|
838
|
+
const { key } = await inquirer.prompt([{
|
|
839
|
+
type: 'password',
|
|
840
|
+
name: 'key',
|
|
841
|
+
message: 'OpenAI API key (sk-...):',
|
|
842
|
+
mask: '*',
|
|
843
|
+
validate: (v) => v.trim().length > 0 || 'The API key is required',
|
|
844
|
+
}]);
|
|
845
|
+
apiToken = key.trim();
|
|
846
|
+
}
|
|
754
847
|
}
|
|
755
|
-
|
|
756
|
-
|
|
848
|
+
// 6. Timezone
|
|
849
|
+
let timezone = (options.timezone || '').trim() || template.config.timezone;
|
|
850
|
+
if (!options.timezone && interactive) {
|
|
851
|
+
({ timezone } = await inquirer.prompt([{
|
|
852
|
+
type: 'input',
|
|
853
|
+
name: 'timezone',
|
|
854
|
+
message: 'Timezone (e.g. America/Lima, America/Bogota, Europe/Madrid):',
|
|
855
|
+
default: template.config.timezone,
|
|
856
|
+
}]));
|
|
757
857
|
}
|
|
858
|
+
finalConfig.timezone = timezone;
|
|
859
|
+
finalConfig.channels = connectedChannels;
|
|
860
|
+
finalConfig.zone = credentials.zone;
|
|
758
861
|
if (apiToken && finalConfig.aiProviders && finalConfig.aiProviders.length > 0) {
|
|
759
862
|
finalConfig.aiProviders[0].apiToken = apiToken;
|
|
760
863
|
}
|
|
761
|
-
//
|
|
762
|
-
if (options.json) {
|
|
763
|
-
console.log(section('Configuration JSON'));
|
|
764
|
-
logger.json(finalConfig);
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
// Paso 6: Confirmacion
|
|
864
|
+
// 7. Summary + confirmation
|
|
768
865
|
console.log(section('Summary'));
|
|
769
|
-
console.log(kvPair('Template', template.name));
|
|
866
|
+
console.log(kvPair('Template', `${template.name} (${template.id})`));
|
|
867
|
+
console.log(kvPair('Agent name', finalConfig.name));
|
|
770
868
|
for (const ph of template.placeholders) {
|
|
771
869
|
console.log(kvPair(ph.label, placeholderAnswers[ph.key]));
|
|
772
870
|
}
|
|
773
|
-
console.log(kvPair('Channels', connectedChannels.length > 0 ? connectedChannels.map(c => `${c.channel} (${c.key})`).join(', ') : 'Not
|
|
774
|
-
console.log(kvPair('OpenAI API
|
|
871
|
+
console.log(kvPair('Channels', connectedChannels.length > 0 ? connectedChannels.map(c => `${c.channel} (${c.key})`).join(', ') : 'Not connected'));
|
|
872
|
+
console.log(kvPair('OpenAI API key', apiToken ? 'Set' : theme.warning('Not set (the agent will not reply without it)')));
|
|
775
873
|
console.log(kvPair('Timezone', timezone));
|
|
776
874
|
console.log(kvPair('Actions', String(finalConfig.actions.length)));
|
|
777
875
|
console.log(kvPair('AI', `${finalConfig.aiProviders[0].provider} / ${finalConfig.aiProviders[0].model}`));
|
|
778
876
|
console.log();
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
default: true,
|
|
784
|
-
}]);
|
|
785
|
-
if (!confirm) {
|
|
786
|
-
logger.warning('Creation cancelled');
|
|
787
|
-
return;
|
|
877
|
+
const ok = await confirmAction('Create the agent with this configuration?', options);
|
|
878
|
+
if (!ok) {
|
|
879
|
+
logger.dim('Aborted.');
|
|
880
|
+
process.exit(1);
|
|
788
881
|
}
|
|
789
|
-
//
|
|
882
|
+
// 8. Create
|
|
790
883
|
const agent = new Agent({
|
|
791
884
|
workspaceId: credentials.workspace,
|
|
792
885
|
apiKey: credentials.apiKey,
|
|
793
886
|
zone: credentials.zone,
|
|
794
887
|
...(options.dev && { customUrl: "http://localhost:5090" })
|
|
795
888
|
});
|
|
796
|
-
finalConfig.zone = credentials.zone;
|
|
797
889
|
const spinner = createSpinner('Creating agent...');
|
|
798
890
|
spinner.start();
|
|
799
|
-
|
|
800
|
-
|
|
891
|
+
let result;
|
|
892
|
+
try {
|
|
893
|
+
result = await agent.addAgent(finalConfig);
|
|
894
|
+
}
|
|
895
|
+
catch (err) {
|
|
896
|
+
spinner.stop();
|
|
897
|
+
throw err;
|
|
898
|
+
}
|
|
899
|
+
spinner.succeed('Agent created');
|
|
900
|
+
const agentId = result.agentId || '<agentId>';
|
|
801
901
|
logger.title('Agent details');
|
|
802
|
-
if (result.agentId)
|
|
902
|
+
if (result.agentId)
|
|
803
903
|
logger.label('ID', result.agentId);
|
|
804
|
-
}
|
|
805
904
|
logger.label('Name', finalConfig.name);
|
|
806
|
-
logger.label('
|
|
905
|
+
logger.label('Template', template.name);
|
|
807
906
|
if (connectedChannels.length > 0) {
|
|
808
907
|
logger.label('Channels', connectedChannels.map(c => `${c.channel} (${c.key})`).join(', '));
|
|
809
908
|
}
|
|
810
909
|
logger.label('Actions', String(finalConfig.actions.length));
|
|
811
910
|
console.log();
|
|
812
911
|
console.log(section('Next steps'));
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
console.log(theme.bold(
|
|
912
|
+
let step = 1;
|
|
913
|
+
if (!apiToken) {
|
|
914
|
+
console.log(theme.bold(` ${step++}.`) + ' Add your OpenAI API key (required): ' + theme.success(`plazbot agent ai-config ${agentId} --provider openai --api-token <key>`));
|
|
816
915
|
}
|
|
817
|
-
|
|
818
|
-
|
|
916
|
+
console.log(theme.bold(` ${step++}.`) + ' Test the agent: ' + theme.success(`plazbot agent chat ${agentId}`));
|
|
917
|
+
if (connectedChannels.length === 0) {
|
|
918
|
+
console.log(theme.bold(` ${step++}.`) + ' Connect a WhatsApp number: ' + theme.success(`plazbot whatsapp assign -p <phone> -a ${agentId}`));
|
|
819
919
|
}
|
|
820
|
-
console.log(theme.bold(` ${connectedChannels.length === 0 ? '3' : '2'}.`) + ' Add your OpenAI API Key if you have not yet');
|
|
821
920
|
console.log();
|
|
822
921
|
if (!apiToken) {
|
|
823
|
-
logger.warning('
|
|
824
|
-
logger.dim(`plazbot agent ai-config ${result.agentId || '<agentId>'}`);
|
|
825
|
-
console.log();
|
|
922
|
+
logger.warning('The agent will not reply until an OpenAI API key is configured.');
|
|
826
923
|
}
|
|
827
|
-
logger.dim('Test agent: plazbot agent chat -a ' + (result.agentId || '<agentId>'));
|
|
828
924
|
if (options.dev) {
|
|
829
925
|
logger.warning('Environment: development');
|
|
830
926
|
}
|
|
831
927
|
}
|
|
832
928
|
catch (error) {
|
|
833
|
-
|
|
834
|
-
logger.error(message);
|
|
835
|
-
process.exit(1);
|
|
929
|
+
fail(error);
|
|
836
930
|
}
|
|
837
931
|
});
|
|
838
932
|
addExamples(templatesCommand, [
|
|
839
|
-
{ description: 'Pick a template and create the agent
|
|
933
|
+
{ description: 'Pick a template and create the agent step by step',
|
|
840
934
|
command: 'plazbot agent templates' },
|
|
841
|
-
{ description: '
|
|
842
|
-
command: 'plazbot agent templates --json' },
|
|
935
|
+
{ description: 'List the available templates as JSON',
|
|
936
|
+
command: 'plazbot agent templates --list --json' },
|
|
937
|
+
{ description: 'Create a clinic agent without prompts',
|
|
938
|
+
command: 'plazbot agent templates -t clinic --name "Clinic Bot" --business "Clinica Sol" --address "Av. Arequipa 123, Lima" --contact-phone "+51 1 234 5678" --api-token sk-xxx --yes' },
|
|
939
|
+
{ description: 'Preview the resulting config (no API calls) and save it for agent create',
|
|
940
|
+
command: 'plazbot agent templates -t sales --business "Acme" --address "Calle 1" --contact-phone "+51912345678" --json > agent.json' },
|
|
941
|
+
]);
|
|
942
|
+
addNotes(templatesCommand, [
|
|
943
|
+
'Templates are written in Spanish and use OpenAI (gpt-4o): the agent needs an OpenAI API key to reply.',
|
|
944
|
+
'Without a terminal (CI, pipes) pass --template, the business flags and --yes. Channels are only offered interactively.',
|
|
945
|
+
'--json never calls the API and leaves the API token out of the output.',
|
|
843
946
|
]);
|