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,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
- // Registro de plantillas con sus placeholders
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: 'Agent for sales management, quotes and orders',
587
+ description: 'Sales management, quotes and orders',
580
588
  industry: 'Sales',
581
589
  placeholders: [
582
- { key: '[NOMBRE DE LA EMPRESA]', label: 'Your company name' },
583
- { key: '[DIRECCION COMPLETA]', label: 'Your company address' },
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: 'Agent for technical support, tickets and problem resolution',
599
+ description: 'Technical support, tickets and troubleshooting',
591
600
  industry: 'Technology',
592
601
  placeholders: [
593
- { key: '[NOMBRE DE LA EMPRESA]', label: 'Your company name' },
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: 'Agent for clinics: medical appointments, schedules and specialties',
611
+ description: 'Medical appointments, schedules and specialties',
602
612
  industry: 'Health',
603
613
  placeholders: [
604
- { key: '[NOMBRE DE LA CLINICA]', label: 'Your clinic name' },
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: 'Agent for restaurants: reservations, menu and delivery',
613
- industry: 'Gastronomy',
623
+ description: 'Reservations, menu and delivery',
624
+ industry: 'Food & beverage',
614
625
  placeholders: [
615
- { key: '[NOMBRE DEL RESTAURANTE]', label: 'Your restaurant name' },
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: 'Agent for real estate: properties, viewings and advisory',
624
- industry: 'Real Estate',
635
+ description: 'Properties, viewings and advisory',
636
+ industry: 'Real estate',
625
637
  placeholders: [
626
- { key: '[NOMBRE DE LA INMOBILIARIA]', label: 'Your real estate name' },
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 pre-configured templates')
658
- .option('--dev', 'Use development environment', false)
659
- .option('--json', 'Only show the resulting JSON without creating the agent', false)
660
- .action(async (options) => {
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
- console.log(section('Agent templates'));
663
- console.log(theme.muted(' Create a pre-configured agent in seconds\n'));
664
- const rows = TEMPLATES.map((t, i) => [
665
- String(i + 1),
666
- t.name,
667
- t.industry,
668
- t.description,
669
- ]);
670
- console.log(createTable(['#', 'Name', 'Industry', 'Description'], rows));
671
- // Paso 1: Seleccionar plantilla
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
- const template = TEMPLATES[selected];
684
- console.log(section(`${template.name}`));
685
- console.log(kvPair('Industry', template.industry));
686
- console.log(kvPair('Persona', `${template.config.person.name} - ${template.config.person.role}`));
687
- console.log(kvPair('Actions', String(template.config.actions.length)));
688
- console.log(kvPair('Tool Calling', 'Enabled'));
689
- console.log();
690
- // Paso 2: Personalizar placeholders
691
- console.log(theme.bold('\n Quick customization'));
692
- console.log(theme.muted(' Fill in your business details:\n'));
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 { value } = await inquirer.prompt([{
696
- type: 'input',
697
- name: 'value',
698
- message: `${ph.label}:`,
699
- validate: (v) => v.trim().length > 0 || 'This field is required',
700
- }]);
701
- placeholderAnswers[ph.key] = value.trim();
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 (wantChannels) {
714
- const wizardCtx = {
715
- zone: credentials.zone,
716
- workspaceId: credentials.workspace,
717
- apiKey: credentials.apiKey,
718
- dev: !!options.dev,
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
- apiToken = key.trim();
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
- // Paso 5: Zona horaria
742
- const { timezone } = await inquirer.prompt([{
743
- type: 'input',
744
- name: 'timezone',
745
- message: 'Timezone (e.g. America/Lima, America/Bogota, America/Mexico_City):',
746
- default: template.config.timezone,
747
- }]);
748
- // Construir config final
749
- const configClone = JSON.parse(JSON.stringify(template.config));
750
- const finalConfig = replacePlaceholders(configClone, placeholderAnswers);
751
- finalConfig.timezone = timezone;
752
- if (connectedChannels.length > 0) {
753
- finalConfig.channels = connectedChannels;
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
- else {
756
- finalConfig.channels = [];
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
- // Si es --json, solo mostrar y salir
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 configured'));
774
- console.log(kvPair('OpenAI API Key', apiToken ? 'Configured' : 'Not configured (the agent will not work without it)'));
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 { confirm } = await inquirer.prompt([{
780
- type: 'confirm',
781
- name: 'confirm',
782
- message: 'Create the agent with this configuration?',
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
- // Paso 7: Crear agente
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
- const result = await agent.addAgent(finalConfig);
800
- spinner.succeed('Agent created successfully');
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('Industry', template.industry);
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
- if (connectedChannels.length === 0) {
814
- console.log(theme.bold(' 1.') + ' Connect channels from: ' + theme.success('plazbot agent chat -a <agentId>'));
815
- console.log(theme.bold(' 2.') + ' Or connect from the platform in Settings > Integrations');
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
- else {
818
- console.log(theme.bold(' 1.') + ' Your agent already has channels connected and enabled');
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('Remember to configure your OpenAI API Key for the agent to work:');
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
- const message = error instanceof Error ? error.message : 'Unknown error';
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 interactively',
933
+ { description: 'Pick a template and create the agent step by step',
840
934
  command: 'plazbot agent templates' },
841
- { description: 'Preview the resulting JSON without creating the agent',
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
  ]);