@scout9/app 1.0.0-alpha.0.4.8 → 1.0.0-alpha.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.
@@ -2,21 +2,21 @@ import polka from 'polka';
2
2
  import sirv from 'sirv';
3
3
  import compression from 'compression';
4
4
  import bodyParser from 'body-parser';
5
- import colors from 'kleur';
6
5
  import { config as dotenv } from 'dotenv';
7
6
  import { Configuration, Scout9Api } from '@scout9/admin';
8
- import { EventResponse } from '@scout9/app';
7
+ import { EventResponse, ProgressLogger } from '@scout9/app';
9
8
  import { WorkflowEventSchema, WorkflowResponseSchema } from '@scout9/app/schemas';
9
+ import { Spirits } from '@scout9/app/spirits';
10
10
  import path, { resolve } from 'node:path';
11
11
  import fs from 'node:fs';
12
12
  import https from 'node:https';
13
13
  import { fileURLToPath, pathToFileURL } from 'node:url';
14
- import projectApp from './src/app.js';
15
- import config from './config.js';
16
14
  import { readdir } from 'fs/promises';
17
15
  import { ZodError } from 'zod';
18
16
  import { fromError } from 'zod-validation-error';
19
-
17
+ import { bgBlack, blue, bold, cyan, green, grey, magenta, red, white } from 'kleur/colors';
18
+ import projectApp from './src/app.js';
19
+ import config from './config.js';
20
20
 
21
21
 
22
22
  const __filename = fileURLToPath(import.meta.url);
@@ -80,7 +80,7 @@ const simplifyZodError = (error, tag = undefined) => {
80
80
  validationError.message = validationError.message.replace('Validation error', tag);
81
81
  }
82
82
  return validationError;
83
- }
83
+ };
84
84
 
85
85
  const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
86
86
  let name = e?.name || 'Runtime Error';
@@ -105,7 +105,7 @@ const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
105
105
  }
106
106
  }
107
107
  if (body) {
108
- console.log(colors.grey(JSON.stringify(body, null, dev ? 2 : undefined)));
108
+ console.log(grey(JSON.stringify(body, null, dev ? 2 : undefined)));
109
109
  }
110
110
  if (tag && typeof tag === 'string') {
111
111
  message = `${tag}: ${message}`;
@@ -113,12 +113,12 @@ const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
113
113
  if (typeof e?.constructor?.name === 'string') {
114
114
  message = `(${e?.constructor?.name}) ${message}`;
115
115
  }
116
- console.log(colors.red(`${colors.bold(`${code} Error`)}: ${message}`));
116
+ console.log(red(`${bold(`${code} Error`)}: ${message}`));
117
117
  if ('stack' in e) {
118
- console.log('STACK:', colors.grey(e.stack));
118
+ console.log('STACK:', grey(e.stack));
119
119
  }
120
120
  if (body) {
121
- console.log('INPUT:', colors.grey(JSON.stringify(body, null, dev ? 2 : undefined)));
121
+ console.log('INPUT:', grey(JSON.stringify(body, null, dev ? 2 : undefined)));
122
122
  }
123
123
  if (res) {
124
124
  res.writeHead(code, {'Content-Type': 'application/json'});
@@ -130,7 +130,16 @@ const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
130
130
  }
131
131
  };
132
132
 
133
- const handleZodError = ({error, res = undefined, code = 500, status, name, bodyLabel = 'Provided Input', body = undefined, action = ''}) => {
133
+ const handleZodError = ({
134
+ error,
135
+ res = undefined,
136
+ code = 500,
137
+ status,
138
+ name,
139
+ bodyLabel = 'Provided Input',
140
+ body = undefined,
141
+ action = ''
142
+ }) => {
134
143
  res?.writeHead?.(code, {'Content-Type': 'application/json'});
135
144
  if (error instanceof ZodError) {
136
145
  const formattedError = simplifyZodError(error);
@@ -139,12 +148,12 @@ const handleZodError = ({error, res = undefined, code = 500, status, name, bodyL
139
148
  error: formattedError.message,
140
149
  errors: [formattedError.message]
141
150
  }));
142
- console.log(colors.red(`${colors.bold(`${name}`)}:`));
151
+ console.log(red(`${bold(`${name}`)}:`));
143
152
  if (body) {
144
- console.log(colors.grey(`${bodyLabel}:`));
145
- console.log(colors.grey(JSON.stringify(body, null, dev ? 2 : undefined)));
153
+ console.log(grey(`${bodyLabel}:`));
154
+ console.log(grey(JSON.stringify(body, null, dev ? 2 : undefined)));
146
155
  }
147
- console.log(colors.red(`${action}${formattedError}`));
156
+ console.log(red(`${action}${formattedError}`));
148
157
  } else {
149
158
  console.error(error);
150
159
  error.message = `${name}: ` + error.message;
@@ -253,7 +262,7 @@ app.post(dev ? '/dev/workflow' : '/', async (req, res) => {
253
262
  } else {
254
263
  return response;
255
264
  }
256
- })
265
+ });
257
266
  } catch (error) {
258
267
  if (error instanceof ZodError) {
259
268
  handleZodError({
@@ -278,8 +287,8 @@ app.post(dev ? '/dev/workflow' : '/', async (req, res) => {
278
287
  try {
279
288
  const formattedResponse = WorkflowResponseSchema.parse(response);
280
289
  if (dev) {
281
- console.log(colors.green(`Workflow Sending Response:`));
282
- console.log(colors.grey(JSON.stringify(formattedResponse, null, 2)));
290
+ console.log(green(`Workflow Sending Response:`));
291
+ console.log(grey(JSON.stringify(formattedResponse, null, 2)));
283
292
  }
284
293
  res.writeHead(200, {'Content-Type': 'application/json'});
285
294
  res.end(JSON.stringify(formattedResponse));
@@ -402,7 +411,7 @@ async function runEntityApi(req, res) {
402
411
  res.writeHead(response.status || 200, {'Content-Type': 'application/json'});
403
412
  res.end(JSON.stringify(data));
404
413
  console.log(`${req.method} EntityApi.${lastSegment}:`);
405
- console.log(colors.grey(JSON.stringify(data)));
414
+ console.log(grey(JSON.stringify(data)));
406
415
  } else {
407
416
  throw new Error(`Invalid response: not an EventResponse`);
408
417
  }
@@ -429,12 +438,13 @@ async function getFilesRecursive(dir) {
429
438
  return results;
430
439
  }
431
440
 
441
+
442
+ const commandsDir = resolve(__dirname, `./src/commands`);
443
+
432
444
  async function runCommandApi(req, res) {
433
445
  let file;
434
446
  const {body, url} = req;
435
447
  const params = url.split('/').slice(2).filter(Boolean);
436
- const commandsDir = resolve(__dirname, `./src/commands`);
437
-
438
448
  try {
439
449
  const files = await getFilesRecursive(commandsDir).then(files => files.map(file => file.replace(commandsDir, '.'))
440
450
  .filter(file => params.every(p => file.includes(p))));
@@ -481,7 +491,14 @@ async function runCommandApi(req, res) {
481
491
  let result;
482
492
 
483
493
  try {
484
- result = await mod.default(body);
494
+ result = await mod.default(body)
495
+ .then((response) => {
496
+ if ('toJSON' in response) {
497
+ return response.toJSON();
498
+ } else {
499
+ return response;
500
+ }
501
+ });
485
502
  } catch (e) {
486
503
  console.error('Failed to run command', e);
487
504
  res.writeHead(500, {'Content-Type': 'application/json'});
@@ -523,6 +540,7 @@ app.post('/entity/:entity/*', runEntityApi);
523
540
  app.delete('/entity/:entity/*', runEntityApi);
524
541
 
525
542
  // For local development: parse a message
543
+ let devProgram;
526
544
  if (dev) {
527
545
 
528
546
  app.get('/dev/config', async (req, res, next) => {
@@ -543,14 +561,14 @@ if (dev) {
543
561
  if (!cache.isTested()) {
544
562
  const testableEntities = config.entities.filter(e => e?.definitions?.length > 0 || e?.training?.length > 0);
545
563
  if (dev && testableEntities.length > 0) {
546
- console.log(`${colors.grey(`${colors.cyan('>')} Testing ${colors.bold(colors.white(testableEntities.length))} Entities...`)}`);
564
+ console.log(`${grey(`${cyan('>')} Testing ${bold(white(testableEntities.length))} Entities...`)}`);
547
565
  const _res = await scout9.parse({
548
566
  message: 'Dummy message to parse',
549
567
  language: 'en',
550
568
  entities: testableEntities
551
569
  });
552
570
  cache.setTested();
553
- console.log(`\t${colors.green(`+ ${testableEntities.length} Entities passed`)}`);
571
+ console.log(`\t${green(`+ ${testableEntities.length} Entities passed`)}`);
554
572
  }
555
573
  }
556
574
  } catch (e) {
@@ -558,25 +576,30 @@ if (dev) {
558
576
  }
559
577
  });
560
578
 
579
+ const devParse = async (message, language = 'en') => {
580
+ if (typeof message !== 'string') {
581
+ throw new Error('Invalid message - expected to be a string');
582
+ }
583
+ console.log(`${grey(`${cyan('>')} Parsing "${bold(white(message))}`)}"`);
584
+ const payload = await scout9.parse({
585
+ message,
586
+ language,
587
+ entities: config.entities
588
+ }).then((_res => _res.data));
589
+ let fields = '';
590
+ for (const [key, value] of Object.entries(payload.context)) {
591
+ fields += `\n\t\t${bold(white(key))}: ${grey(JSON.stringify(value))}`;
592
+ }
593
+ console.log(`\tParsed in ${payload.ms}ms:${grey(`${fields}`)}:`);
594
+ console.log(grey(JSON.stringify(payload)));
595
+ return payload;
596
+ };
597
+
561
598
  app.post('/dev/parse', async (req, res, next) => {
562
599
  try {
563
600
  // req.body: {message: string}
564
- const {message, language} = req.body;
565
- if (typeof message !== 'string') {
566
- throw new Error('Invalid message - expected to be a string');
567
- }
568
- console.log(`${colors.grey(`${colors.cyan('>')} Parsing "${colors.bold(colors.white(message))}`)}"`);
569
- const payload = await scout9.parse({
570
- message,
571
- language: 'en',
572
- entities: config.entities
573
- }).then((_res => _res.data));
574
- let fields = '';
575
- for (const [key, value] of Object.entries(payload.context)) {
576
- fields += `\n\t\t${colors.bold(colors.white(key))}: ${colors.grey(JSON.stringify(value))}`;
577
- }
578
- console.log(`\tParsed in ${payload.ms}ms:${colors.grey(`${fields}`)}:`);
579
- console.log(colors.grey(JSON.stringify(payload)));
601
+ const {message, language = 'en'} = req.body;
602
+ const payload = await devParse(message, language);
580
603
  res.writeHead(200, {'Content-Type': 'application/json'});
581
604
  res.end(JSON.stringify(payload));
582
605
  } catch (e) {
@@ -584,13 +607,18 @@ if (dev) {
584
607
  }
585
608
  });
586
609
 
610
+ const devForward = async (convo) => {
611
+ console.log(`${grey(`${cyan('>')} Forwarding...`)}`);
612
+ const payload = await scout9.forward({convo}).then((_res => _res.data));
613
+ console.log(`\tForwarded in ${payload?.ms}ms`);
614
+ return payload;
615
+ };
616
+
587
617
  app.post('/dev/forward', async (req, res, next) => {
588
618
  try {
589
619
  // req.body: {message: string}
590
620
  const {convo} = req.body;
591
- console.log(`${colors.grey(`${colors.cyan('>')} Forwarding...`)}`);
592
- const payload = await scout9.forward({convo}).then((_res => _res.data));
593
- console.log(`\tForwarded in ${payload?.ms}ms`);
621
+ const payload = await devForward(convo);
594
622
  res.writeHead(200, {'Content-Type': 'application/json'});
595
623
  res.end(JSON.stringify(payload));
596
624
  } catch (e) {
@@ -598,40 +626,399 @@ if (dev) {
598
626
  }
599
627
  });
600
628
 
629
+ const devGenerate = async (messages, personaId) => {
630
+ if (typeof messages !== 'object' || !Array.isArray(messages)) {
631
+ throw new Error('Invalid messages array - expected to be an array of objects');
632
+ }
633
+ if (typeof personaId !== 'string') {
634
+ throw new Error('Invalid persona - expected to be a string');
635
+ }
636
+ const persona = (config.persona || config.agents).find(p => p.id === personaId);
637
+ if (!persona) {
638
+ throw new Error(`Could not find persona with id: ${personaId}, ensure your project is sync'd by running "scout9 sync"`);
639
+ }
640
+ console.log(`${grey(`${cyan('>')} Determining ${bold(white(persona.firstName))}'s`)} response`);
641
+ const payload = await scout9.generate({
642
+ messages,
643
+ persona,
644
+ llm: config.llm,
645
+ pmt: config.pmt
646
+ }).then((_res => _res.data));
647
+ console.log(`\t${grey(`Response: ${green('"')}${bold(white(payload.message))}`)}${green(
648
+ '"')} (elapsed ${payload.ms}ms)`);
649
+
650
+ return payload;
651
+ };
652
+
601
653
  app.post('/dev/generate', async (req, res, next) => {
602
654
  try {
603
655
  // req.body: {conversation: {}, messages: []}
604
656
  const {messages, persona: personaId} = req.body;
605
- if (typeof messages !== 'object' || !Array.isArray(messages)) {
606
- throw new Error('Invalid messages array - expected to be an array of objects');
607
- }
608
- if (typeof personaId !== 'string') {
609
- throw new Error('Invalid persona - expected to be a string');
610
- }
611
- const persona = (config.persona || config.agents).find(p => p.id === personaId);
612
- if (!persona) {
613
- throw new Error(`Could not find persona with id: ${personaId}, ensure your project is sync'd by running "scout9 sync"`);
614
- }
615
- console.log(`${colors.grey(`${colors.cyan('>')} Generating ${colors.bold(colors.white(persona.firstName))}'s`)} ${colors.bold(
616
- colors.red(colors.bgBlack('auto-reply')))}`);
617
- const payload = await scout9.generate({
618
- messages,
619
- persona,
620
- llm: config.llm,
621
- pmt: config.pmt
622
- }).then((_res => _res.data));
623
- console.log(`\t${colors.grey(`Response: ${colors.green('"')}${colors.bold(colors.white(payload.message))}`)}${colors.green(
624
- '"')} (elapsed ${payload.ms}ms)`);
657
+ const payload = await devGenerate(messages, personaId);
625
658
  res.writeHead(200, {'Content-Type': 'application/json'});
626
659
  res.end(JSON.stringify(payload));
627
660
  } catch (e) {
628
661
  handleError(e, res);
629
662
  }
630
663
  });
664
+
665
+
666
+ // NOTE: This does not sync with localhost app, that uses its own state
667
+ devProgram = async () => {
668
+ // Start program where use can test via command console
669
+ const {createInterface} = await import('node:readline');
670
+ const rl = createInterface({
671
+ input: process.stdin,
672
+ output: process.stdout
673
+ });
674
+
675
+ const persona = (config.persona || config.agents)?.[0];
676
+ if (!persona) {
677
+ throw new Error(`A persona is required before processing`);
678
+ }
679
+
680
+ /**
681
+ * @returns {Omit<WorkflowEvent, 'message'> & {command?: CommandConfiguration}}
682
+ */
683
+ const createState = () => ({
684
+ messages: [],
685
+ conversation: {
686
+ $id: 'dev_console_input',
687
+ $agent: persona.id,
688
+ $customer: 'temp',
689
+ environment: 'web'
690
+ },
691
+ context: {},
692
+ agent: persona,
693
+ customer: {
694
+ firstName: 'test',
695
+ name: 'test'
696
+ },
697
+ intent: {current: null, flow: [], initial: null},
698
+ stagnationCount: 0
699
+ });
700
+
701
+ /** @type {Omit<WorkflowEvent, 'message'> & {command?: CommandConfiguration}} */
702
+ let state = createState();
703
+
704
+
705
+ async function processCustomerMessage(message, callback) {
706
+ const messagePayload = {
707
+ id: `user_test_${Date.now()}`,
708
+ role: 'customer',
709
+ content: message,
710
+ time: new Date().toISOString()
711
+ };
712
+ const logger = new ProgressLogger('Processing...');
713
+ if (state.conversation.locked) {
714
+ logger.error(`Conversation locked - ${state.conversation.lockedReason ?? 'Unknown reason'}`);
715
+ return;
716
+ }
717
+ const addMessage = (payload) => {
718
+ state.messages.push(payload);
719
+ switch (payload.role) {
720
+ case 'system':
721
+ logger.write(magenta('system: ' + payload.content));
722
+ break;
723
+ case 'user':
724
+ case 'customer':
725
+ logger.write(green(`> ${state.agent.firstName ? state.agent.firstName + ': ' : ''}` + payload.content));
726
+ break;
727
+ case 'agent':
728
+ case 'assistant':
729
+ logger.write(blue(`> ${state.customer.name ? state.customer.name + ': ' : ''}` + payload.content));
730
+ break;
731
+ default:
732
+ logger.write(red(`UNKNOWN (${payload.role}) + ${payload.content}`));
733
+ }
734
+ };
735
+
736
+ const updateMessage = (payload) => {
737
+ const index = state.messages.findIndex(m => m.id === payload.id);
738
+ if (index < 0) {
739
+ throw new Error(`Cannot find message ${payload.id}`);
740
+ }
741
+ state.messages[index] = payload;
742
+ };
743
+
744
+ const removeMessage = (payload) => {
745
+ if (typeof payload !== 'string') {
746
+ throw new Error(`Invalid payload`);
747
+ }
748
+ const index = state.messages.findIndex(m => m.id === payload.id);
749
+ if (index < 0) {
750
+ throw new Error(`Cannot find message ${payload.id}`);
751
+ }
752
+ state.messages.splice(index, 1);
753
+ };
754
+
755
+ const updateConversation = (payload) => {
756
+ Object.assign(state.conversation, payload);
757
+ };
758
+
759
+ const updateContext = (payload) => {
760
+ Object.assign(state.context, payload);
761
+ };
762
+
763
+ addMessage(messagePayload);
764
+ const result = await Spirits.customer({
765
+ customer: state.customer,
766
+ config,
767
+ parser: async (_msg, _lng) => {
768
+ logger.log(`Parsing...`);
769
+ return devParse(_msg, _lng);
770
+ },
771
+ workflow: async (workflowEvent) => {
772
+ // Set the global variables for the workflows/commands to run Scout9 Macros
773
+ globalThis.SCOUT9 = {
774
+ ...workflowEvent,
775
+ $convo: state.conversation.$id ?? state.conversation.id
776
+ };
777
+
778
+ logger.log(`Gathering ${state.command ? 'Command ' + state.command.entity + ' ' : ''}instructions...`);
779
+ if (state.command) {
780
+ const commandFilePath = resolve(commandsDir, state.command.path);
781
+ let mod;
782
+ try {
783
+ mod = await import(commandFilePath);
784
+ } catch (e) {
785
+ logger.error(`Unable to resolve command ${state.command.entity} at ${commandFilePath}`);
786
+ throw new Error('Failed to gather command instructions');
787
+ }
788
+
789
+ if (!mod || !mod.default) {
790
+ logger.error(`Unable to run command ${state.command.entity} at ${commandFilePath} - must return a default function that returns a WorkflowEvent payload`);
791
+ throw new Error('Failed to run command instructions');
792
+ }
793
+
794
+ try {
795
+
796
+ return mod.default(workflowEvent)
797
+ .then((response) => {
798
+ if ('toJSON' in response) {
799
+ return response.toJSON();
800
+ } else {
801
+ return response;
802
+ }
803
+ })
804
+ .then(WorkflowResponseSchema.parse);
805
+ } catch (e) {
806
+ logger.error(`Failed to run command - ${e.message}`);
807
+ throw e;
808
+ }
809
+
810
+ } else {
811
+ return projectApp(workflowEvent)
812
+ .then((response) => {
813
+ if ('toJSON' in response) {
814
+ return response.toJSON();
815
+ } else {
816
+ return response;
817
+ }
818
+ })
819
+ .then(WorkflowResponseSchema.parse);
820
+ }
821
+ },
822
+ generator: async (request) => {
823
+ logger.log(`Determining response...`);
824
+ const personaId = typeof request.persona === 'string' ? request.persona : request.persona.id;
825
+ return devGenerate(request.messages, personaId);
826
+ },
827
+ idGenerator: (prefix) => `${prefix}_test_${Date.now()}`,
828
+ progress: (
829
+ message,
830
+ level,
831
+ type,
832
+ payload
833
+ ) => {
834
+ callback(message, level);
835
+ if (type) {
836
+ switch (type) {
837
+ case 'ADD_MESSAGE':
838
+ addMessage(payload);
839
+ break;
840
+ case 'UPDATE_MESSAGE':
841
+ updateMessage(payload);
842
+ break;
843
+ case 'REMOVE_MESSAGE':
844
+ removeMessage(payload);
845
+ break;
846
+ case 'UPDATE_CONVERSATION':
847
+ updateConversation(payload);
848
+ break;
849
+ case 'UPDATE_CONTEXT':
850
+ updateContext(payload);
851
+ break;
852
+ case 'SET_PROCESSING':
853
+ break;
854
+ default:
855
+ throw new Error(`Unknown progress type: ${type}`);
856
+ }
857
+ }
858
+ },
859
+ message: messagePayload,
860
+ context: state.context,
861
+ messages: state.messages,
862
+ conversation: state.conversation
863
+ });
864
+
865
+ // If a forward happens (due to a lock or other reason)
866
+ if (!!result.conversation.forward) {
867
+ if (!state.conversation.locked) {
868
+ // Only forward if conversation is not already locked
869
+ await devForward(state.conversation.$id);
870
+ }
871
+ updateConversation({locked: true});
872
+ logger.error(`Conversation locked`);
873
+ return;
874
+ }
875
+
876
+ // Process changes as a success
877
+
878
+ // Update conversation (assuming it's changed)
879
+ if (result.conversation.after) {
880
+ updateConversation(result.conversation.after);
881
+ }
882
+
883
+ // Update conversation context (assuming it's changed)
884
+ if (result.context) {
885
+ updateContext(result.context.after);
886
+ }
887
+
888
+ if (!result.messages.after.find(m => m.id === result.message.after.id)) {
889
+ console.error(`Message not found in result.messages.after`, result.message.after.id);
890
+ result.messages.after.push(result.message.after);
891
+ }
892
+
893
+ // Sync messages state update/add/delete
894
+ for (const message of result.messages.after) {
895
+ // Did this exist?
896
+ const existed = !!result.messages.before.find(m => m.id === message.id);
897
+ if (existed) {
898
+ updateMessage(message);
899
+ } else {
900
+ addMessage(message);
901
+ }
902
+ }
903
+ for (const message of result.messages.before) {
904
+ const exists = !!result.messages.after.find(m => m.id === message.id);
905
+ if (!exists) {
906
+ removeMessage(message.id);
907
+ }
908
+ }
909
+
910
+ logger.done();
911
+ }
912
+
913
+ /**
914
+ * @param {CommandConfiguration} command
915
+ * @param {string} message
916
+ * @param callback
917
+ * @returns {Promise<void>}
918
+ */
919
+ async function processCommand(command, message, callback) {
920
+ console.log(magenta(`> command <${command.entity}>`));
921
+ state = createState();
922
+ state.command = command;
923
+ return processCustomerMessage(`Assist me in this ${command.entity} flow`, callback);
924
+ }
925
+
926
+ async function devProgramProcessInput(message, callback) {
927
+ // Check if internal command
928
+ switch (message.toLowerCase().trim()) {
929
+ case 'context':
930
+ console.log(white('> Current Conversation Context:'));
931
+ console.log(grey(JSON.stringify(state.context)));
932
+ return;
933
+ case 'conversation':
934
+ case 'convo':
935
+ console.log(white('> Current Conversation State:'));
936
+ console.log(grey(JSON.stringify(state.conversation)));
937
+ return;
938
+ case 'messages':
939
+ state.messages.forEach((msg) => {
940
+ switch (msg.role) {
941
+ case 'system':
942
+ console.log(magenta('\t - ' + msg.content));
943
+ break;
944
+ case 'user':
945
+ case 'customer':
946
+ console.log(green('> ' + msg.content));
947
+ break;
948
+ case 'agent':
949
+ case 'assistant':
950
+ console.log(blue('> ' + msg.content));
951
+ break;
952
+ default:
953
+ console.log(red(`UNKNOWN (${msg.role}) + ${msg.content}`));
954
+ }
955
+ });
956
+ return;
957
+ }
958
+
959
+ // Check if it's a command
960
+ const target = message.toLowerCase().trim();
961
+ const command = config.commands.find(command => {
962
+ return command.entity === target;
963
+ });
964
+ // Run the command
965
+ if (command) {
966
+ return processCommand(command, message, callback);
967
+ }
968
+
969
+ // Otherwise default to processing customer message
970
+ return processCustomerMessage(message, callback);
971
+ }
972
+
973
+ // Function to ask for input, perform the task, and then ask again
974
+ function promptUser() {
975
+ rl.question('> ', async (input) => {
976
+ if (input.toLowerCase() === 'exit') {
977
+ rl.close();
978
+ } else {
979
+ if (input) {
980
+ await devProgramProcessInput(input, () => {
981
+ //
982
+ });
983
+ }
984
+ promptUser();
985
+ }
986
+ });
987
+ }
988
+
989
+
990
+
991
+ console.log(grey(`\nThe following ${bold('commands')} are available...`));
992
+ [['context', 'logs the state context inserted into the conversation'], ['conversation', 'logs conversation details'], ['messages', 'logs all message history']].forEach(([command, description]) => {
993
+ console.log(`\t - ${magenta(command)} ${grey(description)}`);
994
+ });
995
+
996
+ if (config.commands.length) {
997
+ console.log(grey(`\nThe following ${bold('custom commands')} are available...`));
998
+ }
999
+ config.commands.forEach((command) => {
1000
+ console.log(magenta(`\t - ${command.entity}`));
1001
+ });
1002
+
1003
+ // Start the first prompt
1004
+
1005
+ console.log(white(`\nType and hit enter to test your PMT responses...\n`));
1006
+ promptUser();
1007
+
1008
+ // Handle Ctrl+C (SIGINT) signal to exit gracefully
1009
+ rl.on('SIGINT', () => {
1010
+ rl.close();
1011
+ process.exit(0);
1012
+ });
1013
+
1014
+
1015
+ };
1016
+
1017
+
631
1018
  }
632
1019
 
633
1020
 
634
- app.listen(process.env.PORT || 8080, err => {
1021
+ app.listen(process.env.PORT || 8080, async (err) => {
635
1022
  if (err) throw err;
636
1023
 
637
1024
  const art_scout9 = `
@@ -647,6 +1034,8 @@ app.listen(process.env.PORT || 8080, err => {
647
1034
  \\|_________|
648
1035
  `;
649
1036
  const art_pmt = `
1037
+
1038
+
650
1039
  _______ __ __ ________
651
1040
  | \ | \ / \| \
652
1041
  | $$$$$$$\| $$\ / $$ \$$$$$$$$
@@ -657,34 +1046,35 @@ app.listen(process.env.PORT || 8080, err => {
657
1046
  | $$ | $$ \$ | $$ | $$
658
1047
  \$$ \$$ \$$ \$$
659
1048
 
660
-
661
-
662
1049
  `;
663
1050
  const protocol = process.env.PROTOCOL || 'http';
664
1051
  const host = process.env.HOST || 'localhost';
665
1052
  const port = process.env.PORT || 8080;
666
1053
  const fullUrl = `${protocol}://${host}:${port}`;
667
1054
  if (dev) {
668
- console.log(colors.bold(colors.green(art_scout9)));
669
- console.log(colors.bold(colors.cyan(art_pmt)));
670
- console.log(`${colors.grey(`${colors.cyan('>')} Running ${colors.bold(colors.white('Scout9'))}`)} ${colors.bold(
671
- colors.red(colors.bgBlack('auto-reply')))} ${colors.grey('dev environment on')} ${fullUrl}`);
1055
+ console.log(bold(green(art_scout9)));
1056
+ console.log(bold(cyan(art_pmt)));
1057
+ console.log(`${grey(`${cyan('>')} Running ${bold(white('Scout9'))}`)} ${grey('dev environment on')} ${fullUrl}`);
672
1058
  } else {
673
- console.log(`Running Scout9 auto-reply app on ${fullUrl}`);
1059
+ console.log(`Running Scout9 app on ${fullUrl}`);
674
1060
  }
675
1061
  // Run checks
676
1062
  if (!fs.existsSync(configFilePath)) {
677
- console.log(colors.red('Missing .env file, your auto reply application may not work without it.'));
1063
+ console.log(red('Missing .env file, your PMT application may not work without it.'));
678
1064
  }
679
1065
 
680
1066
  if (dev && !process.env.SCOUT9_API_KEY) {
681
- console.log(colors.red(
682
- 'Missing SCOUT9_API_KEY environment variable, your auto reply application may not work without it.'));
1067
+ console.log(red(
1068
+ 'Missing SCOUT9_API_KEY environment variable, your PMT application may not work without it.'));
683
1069
  }
684
1070
 
685
1071
  if (process.env.SCOUT9_API_KEY === '<insert-scout9-api-key>') {
686
- console.log(`${colors.red('SCOUT9_API_KEY has not been set in your .env file.')} ${colors.grey(
687
- 'You can find your API key in the Scout9 dashboard.')} ${colors.bold(colors.cyan('https://scout9.com'))}`);
1072
+ console.log(`${red('SCOUT9_API_KEY has not been set in your .env file.')} ${grey(
1073
+ 'You can find your API key in the Scout9 dashboard.')} ${bold(cyan('https://scout9.com'))}`);
1074
+ }
1075
+
1076
+ if (dev) {
1077
+ devProgram();
688
1078
  }
689
1079
 
690
1080
  });