@scout9/app 1.0.0-alpha.0.1.9 → 1.0.0-alpha.0.1.90

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 (70) hide show
  1. package/README.md +32 -0
  2. package/dist/{index-92deaa5f.cjs → exports-e7d51b70.cjs} +46618 -4591
  3. package/dist/index.cjs +58 -15
  4. package/dist/{multipart-parser-090f08a9.cjs → multipart-parser-e09a67c9.cjs} +13 -7
  5. package/dist/spirits-3b603262.cjs +1218 -0
  6. package/dist/spirits.cjs +9 -0
  7. package/dist/testing-tools.cjs +48 -0
  8. package/package.json +30 -8
  9. package/src/cli.js +162 -69
  10. package/src/core/config/agents.js +300 -7
  11. package/src/core/config/entities.js +58 -28
  12. package/src/core/config/index.js +37 -15
  13. package/src/core/config/project.js +160 -6
  14. package/src/core/config/workflow.js +13 -12
  15. package/src/core/data.js +27 -0
  16. package/src/core/index.js +386 -137
  17. package/src/core/sync.js +71 -0
  18. package/src/core/templates/Dockerfile +22 -0
  19. package/src/core/templates/app.js +453 -0
  20. package/src/core/templates/project-files.js +36 -0
  21. package/src/core/templates/template-package.json +13 -0
  22. package/src/exports.js +21 -17
  23. package/src/platform.js +189 -33
  24. package/src/public.d.ts.text +330 -0
  25. package/src/report.js +117 -0
  26. package/src/runtime/client/api.js +56 -159
  27. package/src/runtime/client/config.js +60 -11
  28. package/src/runtime/client/entity.js +19 -6
  29. package/src/runtime/client/index.js +5 -3
  30. package/src/runtime/client/message.js +13 -3
  31. package/src/runtime/client/platform.js +86 -0
  32. package/src/runtime/client/{agent.js → users.js} +35 -3
  33. package/src/runtime/client/utils.js +10 -9
  34. package/src/runtime/client/workflow.js +131 -9
  35. package/src/runtime/entry.js +2 -2
  36. package/src/testing-tools/dev.js +373 -0
  37. package/src/testing-tools/index.js +1 -0
  38. package/src/testing-tools/mocks.js +37 -5
  39. package/src/testing-tools/spirits.js +530 -0
  40. package/src/utils/audio-buffer.js +16 -0
  41. package/src/utils/audio-type.js +27 -0
  42. package/src/utils/configs/agents.js +68 -0
  43. package/src/utils/configs/entities.js +145 -0
  44. package/src/utils/configs/project.js +23 -0
  45. package/src/utils/configs/workflow.js +47 -0
  46. package/src/utils/file-type.js +569 -0
  47. package/src/utils/file.js +158 -0
  48. package/src/utils/glob.js +30 -0
  49. package/src/utils/image-buffer.js +23 -0
  50. package/src/utils/image-type.js +39 -0
  51. package/src/utils/index.js +1 -0
  52. package/src/utils/is-svg.js +37 -0
  53. package/src/utils/logger.js +111 -0
  54. package/src/utils/module.js +14 -25
  55. package/src/utils/project-templates.js +191 -0
  56. package/src/utils/project.js +387 -0
  57. package/src/utils/video-type.js +29 -0
  58. package/types/index.d.ts +7588 -206
  59. package/types/index.d.ts.map +97 -22
  60. package/dist/index-1b8d7dd2.cjs +0 -49555
  61. package/dist/index-2ccb115e.cjs +0 -49514
  62. package/dist/index-66b06a30.cjs +0 -49549
  63. package/dist/index-bc029a1d.cjs +0 -49528
  64. package/dist/index-d9a93523.cjs +0 -49527
  65. package/dist/multipart-parser-1508046a.cjs +0 -413
  66. package/dist/multipart-parser-7007403a.cjs +0 -413
  67. package/dist/multipart-parser-70c32c1d.cjs +0 -413
  68. package/dist/multipart-parser-71dec101.cjs +0 -413
  69. package/dist/multipart-parser-f15bf2e0.cjs +0 -414
  70. package/src/public.d.ts +0 -209
@@ -0,0 +1,30 @@
1
+ export function normalizeGlobPattern(pattern) {
2
+ let normalizedPattern = pattern;
3
+ let matchFound = /\{([^,]+),?.*?\}/.test(normalizedPattern);
4
+ let iterations = 0; // Safeguard against potential infinite loops
5
+ const maxIterations = 100; // Set a reasonable limit based on expected complexity
6
+
7
+ while (matchFound && iterations < maxIterations) {
8
+ normalizedPattern = normalizedPattern.replace(/\{([^,]+),?.*?\}/, '$1');
9
+ matchFound = /\{([^,]+),?.*?\}/.test(normalizedPattern);
10
+ iterations++;
11
+ }
12
+
13
+ if (iterations >= maxIterations) {
14
+ console.warn('Reached max iterations - pattern may be too complex or improperly formatted');
15
+ }
16
+
17
+ return normalizedPattern;
18
+ }
19
+
20
+ function escapeSquareBrackets(pattern) {
21
+ // Escaping both opening and closing square brackets
22
+ return pattern.replace(/\[/g, '\\[').replace(/\]/g, '\\]');
23
+ }
24
+
25
+ export function formatGlobPattern(pattern) {
26
+ return escapeSquareBrackets(pattern);
27
+ }
28
+
29
+
30
+
@@ -0,0 +1,23 @@
1
+ import { toBuffer } from './file.js';
2
+ import { imageExtensions } from './image-type.js';
3
+ import { isSvg } from './is-svg.js';
4
+
5
+ /**
6
+ *
7
+ * @param {string | Buffer} img
8
+ * @param [allowSvg=false]
9
+ * @param [source='']
10
+ * @returns {Promise<{buffer: Buffer, ext: string, mime: string, isAudio: boolean, isVideo: boolean, isImage: boolean}>}
11
+ */
12
+ export default async function imageBuffer(img, allowSvg = false, source = '') {
13
+ const imageResult = await toBuffer(img, source);
14
+ if (!imageResult) {
15
+ throw new Error(`Invalid image type: ${typeof img}`);
16
+ }
17
+ if (!imageExtensions.has(imageResult.ext)) {
18
+ if (!(allowSvg && isSvg(img.toString('utf-8')))) {
19
+ throw new Error(`Invalid image type: ${imageResult.ext}`);
20
+ }
21
+ }
22
+ return imageResult;
23
+ }
@@ -0,0 +1,39 @@
1
+ import { fileTypeFromBuffer } from './file-type.js';
2
+
3
+ export const imageExtensions = new Set([
4
+ 'jpg',
5
+ 'png',
6
+ 'gif',
7
+ 'webp',
8
+ 'flif',
9
+ 'cr2',
10
+ 'tif',
11
+ 'bmp',
12
+ 'jxr',
13
+ 'psd',
14
+ 'ico',
15
+ 'bpg',
16
+ 'jp2',
17
+ 'jpm',
18
+ 'jpx',
19
+ 'heic',
20
+ 'cur',
21
+ 'dcm',
22
+ 'avif'
23
+ ]);
24
+
25
+ /**
26
+ * @typedef {Object} ImageTypeResult
27
+ * @property {string} ext - One of the supported [file types](https://github.com/sindresorhus/image-type#supported-file-types).
28
+ * @property {string} mime - The detected [MIME type](https://en.wikipedia.org/wiki/Internet_media_type).
29
+ */
30
+
31
+ /**
32
+ *
33
+ * @param {Buffer | Uint8Array} input
34
+ * @returns {Promise<ImageTypeResult | undefined>}
35
+ */
36
+ export default async function imageType(input) {
37
+ const result = await fileTypeFromBuffer(input);
38
+ return imageExtensions.has(result?.ext) && result;
39
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './error.js';
2
+ export * from './logger.js';
2
3
  export * from './module.js';
3
4
  export * from './stats.js';
@@ -0,0 +1,37 @@
1
+ import { XMLParser, XMLValidator } from 'fast-xml-parser';
2
+
3
+ export function isSvg(str) {
4
+ if (typeof str !== 'string') {
5
+ throw new TypeError(`Expected a \`string\`, got \`${typeof str}\``);
6
+ }
7
+
8
+ str = str.trim();
9
+
10
+ if (str.length === 0) {
11
+ return false;
12
+ }
13
+
14
+ // Has to be `!==` as it can also return an object with error info.
15
+ if (XMLValidator.validate(str) !== true) {
16
+ return false;
17
+ }
18
+
19
+ let jsonObject;
20
+ const parser = new XMLParser();
21
+
22
+ try {
23
+ jsonObject = parser.parse(str);
24
+ } catch {
25
+ return false;
26
+ }
27
+
28
+ if (!jsonObject) {
29
+ return false;
30
+ }
31
+
32
+ if (!('svg' in jsonObject)) {
33
+ return false;
34
+ }
35
+
36
+ return true;
37
+ }
@@ -0,0 +1,111 @@
1
+ import readline from 'node:readline';
2
+ import logUpdate from 'log-update';
3
+ import colors from 'kleur';
4
+
5
+ export class ProgressLogger {
6
+ constructor(initialMessage = null) {
7
+ this.lines = [];
8
+ this._indents = 0;
9
+ this._interval = -1;
10
+ this._frames = [
11
+ "010010",
12
+ "001100",
13
+ "100101",
14
+ "111010",
15
+ "111101",
16
+ "010111",
17
+ "101011",
18
+ "111000",
19
+ "110011",
20
+ "110101"
21
+ ]
22
+ this._framesSpeed = 80;
23
+ if (initialMessage) {
24
+ this.log(initialMessage);
25
+ this.write(initialMessage)
26
+ }
27
+ }
28
+
29
+ set indents(value) {
30
+ this._indents = value;
31
+ }
32
+
33
+ log(message, ...args) {
34
+ const formatted = this._format(message);
35
+
36
+ // const frames = ['-', '\\', '|', '/'];
37
+ let index = 0;
38
+
39
+ logUpdate.clear();
40
+ clearInterval(this._interval);
41
+ this._interval = setInterval(() => {
42
+ const frame = this._frames[index = ++index % this._frames.length];
43
+ logUpdate(`${colors.bold(colors.green(frame))}: ${colors.white(formatted)}`);
44
+ }, this._framesSpeed);
45
+
46
+ this.lines.push({message: formatted, type: 'log'});
47
+ }
48
+
49
+ info(message, ...args) {
50
+ return this.write('\t - ' + colors.italic(colors.gray(message)));
51
+ }
52
+
53
+ primary(message, ...args) {
54
+ return this.write(`${colors.cyan(message)}`);
55
+ }
56
+
57
+ success(message, ...args) {
58
+ return this.write(`✅ ${colors.green(message)}`);
59
+ }
60
+
61
+ error(message, ...args) {
62
+ return this.write(`❌ ${colors.red(message)}`);
63
+ }
64
+
65
+ warn(message, ...args) {
66
+ return this.write(`⚠️ ${colors.yellow(message)}`);
67
+ }
68
+
69
+ write(newMessage) {
70
+ if (this.lines.length === 0) {
71
+ throw new Error("No lines to update");
72
+ }
73
+
74
+ logUpdate.clear();
75
+ clearInterval(this._interval);
76
+ logUpdate.done();
77
+
78
+ // Update the last line in the array
79
+ const lastLine = this.lines[this.lines.length - 1];
80
+ if (lastLine.type === 'log') {
81
+ // Replace the last log line
82
+ this.lines[this.lines.length - 1] = {message: this._format(newMessage), type: 'written'};
83
+ } else {
84
+ // Append the new line
85
+ this.lines.push({message: this._format(newMessage), type: 'written'});
86
+ }
87
+
88
+ // Clear all the logged lines and reprint them
89
+ readline.moveCursor(process.stdout, 0, -this.lines.length);
90
+ readline.clearScreenDown(process.stdout);
91
+ this.lines.forEach(line => console.log(line.message));
92
+
93
+ // // Move up to the last line
94
+ // readline.moveCursor(process.stdout, 0, -1);
95
+ // readline.clearLine(process.stdout, 0);
96
+ // readline.cursorTo(process.stdout, 0);
97
+
98
+ // Log the updated line
99
+ // console.log(newMessage);
100
+ }
101
+
102
+ done() {
103
+ logUpdate.clear();
104
+ logUpdate.done();
105
+ }
106
+
107
+ _format(message) {
108
+ const indentText = '\t'.repeat(this._indents);
109
+ return `${indentText}${message}`;
110
+ }
111
+ }
@@ -1,23 +1,8 @@
1
- async function importFile(filePath) {
2
- return import(filePath);
3
- //
4
- // if (path.extname(filePath) === '.ts') {
5
- // return tsImport.load(filePath, {
6
- // // allowConfigurationWithComments: false,
7
- // useCache: false
8
- // })
9
- // .then((res) => {
10
- // console.log('tsImport.load', res);
11
- // return res;
12
- // })
13
- // .catch((err) => {
14
- // console.log('tsImport.load fail', err);
15
- // throw err;
16
- // });
17
- // } else {
18
- // return import(filePath);
19
- // }
20
- //
1
+ import { pathToFileURL } from 'node:url';
2
+
3
+ export async function importFile(filePath) {
4
+ const fileUrl = pathToFileURL(filePath);
5
+ return import(fileUrl.href);
21
6
  }
22
7
 
23
8
  export async function requireProjectFile(filePath) {
@@ -29,11 +14,15 @@ export async function requireProjectFile(filePath) {
29
14
  }
30
15
 
31
16
  export async function requireOptionalProjectFile(filePath) {
32
- try {
33
- return importFile(filePath).catch(() => null);
34
- } catch (e) {
35
- return null;
36
- }
17
+ return importFile(filePath).catch((e) => {
18
+ switch (e.code) {
19
+ case 'ERR_MODULE_NOT_FOUND':
20
+ case 'MODULE_NOT_FOUND':
21
+ return null;
22
+ default:
23
+ throw e;
24
+ }
25
+ });
37
26
  }
38
27
 
39
28
 
@@ -0,0 +1,191 @@
1
+ /**
2
+ * @param {Scout9ProjectBuildConfig['agents']} agents
3
+ * @param {string} exe - file extension
4
+ * @returns {string}
5
+ */
6
+ function agentsTemplate(agents, exe = 'js') {
7
+ return `
8
+ /**
9
+ * Required core entity type: Agents represents you and your team
10
+ * @returns {Array<import('@scout9/app').IAgent>}
11
+ */
12
+ export default function Agents() {
13
+ return ${JSON.stringify(agents, null, 2)};
14
+ }
15
+ `;
16
+ }
17
+
18
+ /**
19
+ * @param {Scout9ProjectBuildConfig} config
20
+ * @param {string} exe - file extension
21
+ * @returns {string}
22
+ *
23
+ */
24
+ function rootTemplate(config, exe = 'js') {
25
+ return `
26
+ /**
27
+ * Configuration for the Scout9 project.
28
+ * @type {import('@scout9/app').IScout9ProjectConfig}
29
+ */
30
+ export default {
31
+
32
+ /**
33
+ * Configure large language model (LLM) settings to generate auto replies
34
+ */
35
+ llm: {
36
+ engine: ${config?.llm?.engine ? `'${config.llm.engine}'` : `'openai'`},
37
+ model: ${config?.llm?.model ? `'${config.llm.model}'` : `'gpt-3.5-turbo'`},
38
+ },
39
+ /**
40
+ * Configure personal model transformer (PMT) settings to align auto replies the agent's tone
41
+ */
42
+ pmt: {
43
+ engine: ${config?.pmt?.engine ? `'${config.pmt.engine}'` : `'scout9'`},
44
+ model: ${config?.pmt?.model ? `'${config.pmt.model}'` : `'orin-2.2'`},
45
+ },
46
+ organization: ${config?.organization ? JSON.stringify(config.organization, null, 2) : `{name: "Organization Name", description: "Organization description."}`},
47
+ initialContext: ${config?.initialContext ? JSON.stringify(config.initialContext, null, 2) : `[]`}
48
+ }
49
+ `;
50
+ }
51
+
52
+ function appTemplate() {
53
+ return `
54
+ /**
55
+ * @param {import('@scout9/app').IWorkflowEvent} event - every workflow receives an event object
56
+ * @returns {Promise<import('@scout9/app').IWorkflowResponse>} - every workflow must return a WorkflowResponse
57
+ */
58
+ export default async function Scout9App(event) {
59
+ return {
60
+ forward: true,
61
+ };
62
+ }
63
+ `;
64
+ }
65
+
66
+ /**
67
+ * @param {ExportedEntityBuildConfig} entity
68
+ * @param {string} exe - file extension
69
+ */
70
+ function entityTemplate(entity, exe = 'js') {
71
+ let {entity: _entity, entities, api, id, ...rest} = entity;
72
+ if (!_entity.endsWith('Entity')) {
73
+ _entity = `${_entity}Entity`;
74
+ }
75
+ return `
76
+ /**
77
+ * ${rest.description || 'Example entity to help us differentiate if a user wants a delivery or pickup order'}
78
+ * @returns {IEntityBuildConfig}
79
+ */
80
+ export default async function ${_entity}() {
81
+ return ${JSON.stringify(rest, null, 2)};
82
+ }
83
+ `;
84
+ }
85
+
86
+ function customersTemplate() {
87
+ return `
88
+ import Scout9CRM from '@scout9/crm';
89
+ import { EventResponse } from '@scout9/app';
90
+
91
+ const crm = new Scout9CRM({apiKey: process.env.SCOUT9_API_KEY});
92
+
93
+
94
+ /**
95
+ * Required entity - use this to sync with your CRM
96
+ *
97
+ * Query customer info from your CRM
98
+ * @returns {Promise<import('@scout9/app').EventResponse<Array<import('@scout9/app').Customer>>>}
99
+ */
100
+ export const QUERY = async ({searchParams}) => {
101
+ const {page, q, orderBy, endAt, startAt, limit} = searchParams;
102
+ const customers = await crm.query(
103
+ q,
104
+ page ? parseInt(page) : undefined,
105
+ limit ? parseInt(limit) : undefined,
106
+ orderBy ? parseInt(orderBy) : undefined,
107
+ endAt ? parseInt(endAt) : undefined,
108
+ startAt ? parseInt(startAt) : undefined,
109
+ );
110
+ return EventResponse.json(customers, {status: 200});
111
+ }
112
+ `;
113
+ }
114
+
115
+ function customerTemplate() {
116
+ return `
117
+ import Scout9CRM from '@scout9/crm';
118
+ import { json } from '@scout9/app';
119
+
120
+ const crm = new Scout9CRM({apiKey: process.env.SCOUT9_API_KEY});
121
+
122
+ /**
123
+ * Get customer info from your CRM
124
+ * @returns {Promise<import('@scout9/app').EventResponse<import('@scout9/app').Customer>>}
125
+ */
126
+ export const GET = async ({params}) => {
127
+ return json(await crm.get(params.customer));
128
+ };
129
+
130
+ /**
131
+ * When a customer is created on scout9's side
132
+ *
133
+ * Example:
134
+ * A new or unrecognized customer has contacted an agent. Scout9 has captured some preliminary information
135
+ * about them and sent this POST request. Take the information to either add a new customer record to your CRM
136
+ * or return an existing customer id to map the correct customer document.
137
+ *
138
+ * @returns {Promise<import('@scout9/app').EventResponse>}
139
+ */
140
+ export const POST = async ({params, body: newCustomer}) => {
141
+ // Scout9 will generate random id for new customers, but whatever id you return back will be used for the new customer
142
+ const {id: crmId} = await crm.add({$id: params.customer, newCustomer});
143
+ return json({success: true, id: crmId}, {status: 200});
144
+ };
145
+
146
+ /**
147
+ * New customer info revealed, use this to save the customer info to your CRM
148
+ *
149
+ * Example:
150
+ * In a conversation, if any new data is found, Scout9 will send a PUT or PATCH request to allow for you to update
151
+ * your CRM accordingly.
152
+ *
153
+ * @returns {Promise<import('@scout9/app').EventResponse>}
154
+ */
155
+ export const PUT = async ({params, body: updatedCustomer}) => {
156
+ // const id = params.customer;
157
+
158
+ console.log('updatedCustomer', updatedCustomer);
159
+ // @TODO REST call to api to save customer info
160
+
161
+ // @TODO REST call to api to save customer info
162
+ return json({success: true}, {status: 200});
163
+ };
164
+
165
+
166
+ /**
167
+ * Customer request to be removed from platform, use this to remove/update the customer info from your CRM
168
+ *
169
+ * Example:
170
+ * A customer has opt-out of text messaging, Scout9 will send a DELETE message for you to remove any data, returning
171
+ * back success: true will then have Scout9 delete any data on their end as well.
172
+ *
173
+ * @returns {Promise<import('@scout9/app').EventResponse>}
174
+ */
175
+ export const DELETE = async ({params, request}) => {
176
+ await crm.remove(params.customer);
177
+ return json({success: true}, {status: 200});
178
+ };
179
+ `
180
+ }
181
+
182
+ export const projectTemplates = {
183
+ entities: {
184
+ agents: agentsTemplate,
185
+ entity: entityTemplate,
186
+ customers: customersTemplate,
187
+ customer: customerTemplate,
188
+ },
189
+ root: rootTemplate,
190
+ app: appTemplate
191
+ }