@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,387 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import fsp from 'node:fs/promises';
4
+ import { config } from 'dotenv';
5
+ import { checkVariableType, requireProjectFile } from './module.js';
6
+ import { globSync } from 'glob';
7
+ import { projectTemplates } from './project-templates.js';
8
+ import { loadUserPackageJson } from './configs/project.js';
9
+ import loadEntitiesConfig from './configs/entities.js';
10
+ import loadWorkflowsConfig from './configs/workflow.js';
11
+ import { Scout9ProjectBuildConfigSchema } from '../runtime/index.js';
12
+ import { ProgressLogger } from './logger.js';
13
+ import { formatGlobPattern, normalizeGlobPattern } from './glob.js';
14
+
15
+ /**
16
+ * Utility class to load and write project files in a targeted way
17
+ */
18
+ class ProjectModule {
19
+
20
+ constructor({
21
+ autoSave = true,
22
+ cwd = process.cwd(),
23
+ src = './src',
24
+ defaultExe = '.js',
25
+ relativeFileSourcePatternWithoutExe,
26
+ templateBuilder,
27
+ logger = new ProgressLogger(),
28
+ } = {}) {
29
+ this.cwd = cwd;
30
+ this.autoSave = autoSave;
31
+ this.logger = logger;
32
+ this.relativeFileSourcePatternWithoutExe = relativeFileSourcePatternWithoutExe;
33
+ if (!defaultExe.startsWith('.')) {
34
+ console.warn(`defaultExe should start with a "." - ${defaultExe}, defaulting to ".${defaultExe}"`);
35
+ defaultExe = `.${defaultExe}`;
36
+ }
37
+ this.defaultExe = defaultExe;
38
+ this.src = src;
39
+ if (!templateBuilder) {
40
+ throw new Error('templateBuilder is required');
41
+ }
42
+ this.templateBuilder = templateBuilder;
43
+ if (path.extname(relativeFileSourcePatternWithoutExe)) {
44
+ throw new Error(`relativeFileSourcePatternWithoutExe should not include the file extension`);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Loads a project module
50
+ * @param {Scout9ProjectBuildConfig | undefined} config - is required if autoSave is true
51
+ * @param {boolean} ignoreModule - if true, will not require the module
52
+ * @returns {Promise<{mod?: function, exe: string; filePath: string}>}
53
+ */
54
+ async load(config = undefined, ignoreModule = false) {
55
+ let filePath;
56
+ try {
57
+ filePath = this._filepath;
58
+ } catch (e) {
59
+ if ('message' in e && e.message.startsWith('Not Found')) {
60
+ const _defaultFilepath = this._defaultFilepath;
61
+ if (fs.existsSync(_defaultFilepath)) {
62
+ // try {
63
+ // const x = this._filepath;
64
+ // } catch (e) {
65
+ // //
66
+ // }
67
+ throw new Error(`Internal: "this._filepath" was unable to derive the default file path "${_defaultFilepath}`);
68
+ }
69
+ if (this.autoSave) {
70
+ if (!config) {
71
+ throw new Error(`Missing required file ${this.relativeFileSourcePatternWithoutExe}, autoSave requires a config object`);
72
+ }
73
+ return this.save(config, ignoreModule, this._defaultFilepath);
74
+ } else {
75
+ throw new Error(`Missing required file ${this.relativeFileSourcePatternWithoutExe}, rerun "scout9 sync" to fix`);
76
+ }
77
+ } else {
78
+ throw e;
79
+ }
80
+ }
81
+
82
+ let mod;
83
+ if (!ignoreModule) {
84
+ mod = await requireProjectFile(filePath);
85
+ }
86
+
87
+ return {
88
+ mod,
89
+ exe: path.extname(filePath),
90
+ filePath
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Writes data into a project file
96
+ * @param {Scout9ProjectBuildConfig} config
97
+ * @param {boolean} ignoreModule - if true, will not require the module (turning this off will speed runtime)
98
+ * @param {string | undefined} filePathOverride - if provided, will override the default file path
99
+ * @returns {Promise<{mod?: function, exe: string; filePath: string}>}
100
+ */
101
+ async save(config, ignoreModule = false, filePathOverride = undefined) {
102
+ const filePath = filePathOverride || this._filepath;
103
+ const content = this.templateBuilder(config);
104
+ if (!content || typeof content !== 'string') {
105
+ throw new Error(`Invalid templateBuilder return value for file "${filePath}"`);
106
+ }
107
+
108
+ // @TODO why this here?
109
+ if (filePath.endsWith('entities/api.js')) {
110
+ throw new Error(`Invalid file path "${filePath}"`);
111
+ }
112
+
113
+ const exists = fs.existsSync(filePath);
114
+ if (!exists) {
115
+ this.logger?.warn?.(`Missing "${filePath}"`);
116
+ }
117
+ // Ensures directory exists
118
+ await fsp.mkdir(path.dirname(filePath), {recursive: true});
119
+ await fsp.writeFile(filePath, content);
120
+ this.logger?.info?.(`${exists ? 'Synced' : 'Created'} "${filePath}"`);
121
+ let mod;
122
+ if (!ignoreModule) {
123
+ mod = await requireProjectFile(filePath);
124
+ }
125
+ return {
126
+ mod,
127
+ exe: this.defaultExe,
128
+ filePath
129
+ };
130
+ }
131
+
132
+ /**
133
+ * @param {Array<Agent> | function(): Array<Agent> | function(): Promise<Array<Agent>>} mod
134
+ * @param {Array<string>} acceptableTypes
135
+ * @returns {Promise<any>}
136
+ */
137
+ async resolve(mod, acceptableTypes = ['async function', 'function', 'array', 'object', 'json object']) {
138
+ let result;
139
+ const entityType = checkVariableType(mod);
140
+ if (!acceptableTypes.includes(entityType)) {
141
+ throw new Error(`Invalid entity type (${entityType})`);
142
+ }
143
+ switch (entityType) {
144
+ case 'async function':
145
+ case 'function':
146
+ result = await mod();
147
+ break;
148
+ case 'array':
149
+ case 'object':
150
+ case 'json object':
151
+ result = mod;
152
+ break;
153
+ default:
154
+ throw new Error(`Invalid entity type (${entityType}) returned at "${path}"`);
155
+ }
156
+ return result;
157
+ }
158
+
159
+ get _filepath() {
160
+ const _filePath = `${this.src}/${this.relativeFileSourcePatternWithoutExe}`;
161
+ const pattern = formatGlobPattern(`${_filePath}.{ts,js,mjs,cjs}`);
162
+ const paths = globSync(
163
+ pattern,
164
+ {cwd: this.cwd, absolute: true}
165
+ );
166
+ if (paths.length === 0) {
167
+ throw new Error(`Not Found: Missing required file ${_filePath}`);
168
+ }
169
+
170
+ if (paths.length > 1) {
171
+ throw new Error(`Input Error: Multiple files found - [${paths.join(', ')}], remove one and rerun "scout9 sync" to fix`);
172
+ }
173
+
174
+ const [filePath] = paths;
175
+
176
+ if (!filePath) {
177
+ throw new Error(`Not Found: No filepath available in ${_filePath}`);
178
+ }
179
+
180
+ return filePath;
181
+ }
182
+
183
+ get _defaultFilepath() {
184
+ return normalizeGlobPattern(`${this.src}/${this.relativeFileSourcePatternWithoutExe}${this.defaultExe}`);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Project Utility class - used to load and write project files
190
+ */
191
+ export default class ProjectFiles {
192
+
193
+ constructor({
194
+ autoSave = true,
195
+ cwd = process.cwd(),
196
+ src = './src',
197
+ logger
198
+ } = {}) {
199
+ this.cwd = cwd;
200
+ this.src = src;
201
+ this.autoSave = autoSave;
202
+
203
+ // Determine default executable file from the src/index.{ts,js,mjs,cjs}
204
+ const rootFilePaths = [
205
+ path.resolve(cwd, src, 'index.ts'),
206
+ path.resolve(cwd, src, 'index.mjs'),
207
+ path.resolve(cwd, src, 'index.js'),
208
+ path.resolve(cwd, src, 'index.cjs')
209
+ ];
210
+ let defaultExe = '.js';
211
+ for (const filepath of rootFilePaths) {
212
+ if (fs.existsSync(filepath)) {
213
+ defaultExe = path.extname(filepath);
214
+ break;
215
+ }
216
+ }
217
+ this.defaultExe = defaultExe;
218
+ this.root = new ProjectModule({
219
+ autoSave,
220
+ cwd,
221
+ src,
222
+ defaultExe,
223
+ logger,
224
+ relativeFileSourcePatternWithoutExe: '{index,config}',
225
+ templateBuilder: projectTemplates.root
226
+ });
227
+ this.app = new ProjectModule({
228
+ autoSave,
229
+ cwd,
230
+ src,
231
+ defaultExe,
232
+ logger,
233
+ relativeFileSourcePatternWithoutExe: 'app',
234
+ templateBuilder: projectTemplates.app
235
+ });
236
+ this.agents = new ProjectModule({
237
+ autoSave,
238
+ cwd,
239
+ src,
240
+ defaultExe,
241
+ logger,
242
+ relativeFileSourcePatternWithoutExe: 'entities/agents/{index,config}',
243
+ templateBuilder: (config) => {
244
+ // console.log({config});
245
+ return projectTemplates.entities.agents(config.agents);
246
+ }
247
+ });
248
+ this.customers = new ProjectModule({
249
+ autoSave,
250
+ cwd,
251
+ src,
252
+ defaultExe,
253
+ logger,
254
+ relativeFileSourcePatternWithoutExe: 'entities/customers/api',
255
+ templateBuilder: projectTemplates.entities.customers
256
+ });
257
+ this.customer = new ProjectModule({
258
+ autoSave,
259
+ cwd,
260
+ src,
261
+ defaultExe,
262
+ logger,
263
+ relativeFileSourcePatternWithoutExe: 'entities/customers/[customer]/api',
264
+ templateBuilder: projectTemplates.entities.customer
265
+ });
266
+ this.logger = logger;
267
+ }
268
+
269
+ /**
270
+ * Loads all the project configuration data
271
+ * @returns {Promise<Scout9ProjectBuildConfig>}
272
+ */
273
+ async load() {
274
+ /**
275
+ * @type {Scout9ProjectBuildConfig}
276
+ */
277
+ const projectConfig = {
278
+ ...(await this._loadEnv()),
279
+ entities: [],
280
+ agents: [],
281
+ workflows: []
282
+ };
283
+
284
+ // Load entities, except for special entities such as ["agents"]
285
+ projectConfig.entities = await loadEntitiesConfig({cwd: this.cwd, src: this.src});
286
+
287
+ // Load agent persona configuration
288
+ const mod = await this.agents.load(projectConfig, false).then(({mod}) => mod.default);
289
+ projectConfig.agents = await this.agents.resolve(
290
+ mod,
291
+ ['async function', 'function', 'array']
292
+ );
293
+
294
+ // Loads workflows
295
+ projectConfig.workflows = await loadWorkflowsConfig({cwd: this.cwd, src: this.src});
296
+
297
+ // Validate the config
298
+ const result = Scout9ProjectBuildConfigSchema.safeParse(projectConfig);
299
+ if (!result.success) {
300
+ result.error.source = `${this.src}/index.js`;
301
+ throw result.error;
302
+ }
303
+
304
+ // Log
305
+ for (const entityConfig of projectConfig.entities) {
306
+ this.logger?.info?.(`Loaded entity ${entityConfig.entities.join('/')}`);
307
+ }
308
+
309
+ return projectConfig;
310
+ }
311
+
312
+ /**
313
+ *
314
+ * @param {Scout9ProjectBuildConfig | undefined} config - is required if autoSave is true
315
+ * @param {(message: string, type: 'info' | 'warn' | 'error' | 'log' | undefined) => void | undefined} cb
316
+ * @returns {Promise<void>}
317
+ */
318
+ async sync(config, cb = (message, type) => {
319
+ }) {
320
+ await this.root.load(config, true);
321
+ await this.root.save(config, true);
322
+
323
+ await this.app.load(config, true);
324
+
325
+ await this.agents.load(config, true);
326
+ await this.agents.save(config, true);
327
+ cb('Synced agents');
328
+
329
+ await this.customers.load(config, true);
330
+ await this.customer.load(config, true);
331
+
332
+ // If entity file doesn't exist, create it
333
+ for (const entityConfig of config.entities) {
334
+ const {entities} = entityConfig;
335
+ if (entities.length === 0) {
336
+ cb(`Entity ${entityConfig.entity} has no entity path references - skipping check`, 'warn');
337
+ continue;
338
+ }
339
+ const paths = entities.join('/');
340
+ const filepath = path.resolve(this.cwd, this.src, `entities/${paths}/api${this.defaultExe}`);
341
+ if (!fs.existsSync(filepath)) {
342
+ await fsp.mkdir(path.dirname(filepath), {recursive: true});
343
+ await fsp.writeFile(filepath, projectTemplates.entities.entity(entityConfig));
344
+ cb(`Missing entity, created ${filepath}`, 'info');
345
+ } else {
346
+ cb(`Existing entity, ${filepath} skipped`, 'info');
347
+ }
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Loads the users local package.json, if they provide a package-s9-test.json, it will load that instead (used for scout9 internal testing)
353
+ * @returns {Promise<Scout9ProjectConfig>}
354
+ */
355
+ async _loadEnv() {
356
+ if (!!process.env.SCOUT9_API_KEY) {
357
+ if (process.env.SCOUT9_API_KEY.includes('insert-scout9-api-key')) {
358
+ throw new Error('Missing SCOUT9_API_KEY, please add your Scout9 API key to your .env file');
359
+ }
360
+ } else {
361
+ const configFilePath = path.resolve(this.cwd, './.env');
362
+ config({path: configFilePath});
363
+ if (!process.env.SCOUT9_API_KEY) {
364
+ const exists = fs.existsSync(configFilePath);
365
+ if (!exists) {
366
+ throw new Error(`Missing .env file with "SCOUT9_API_KEY".\n\n\tTo fix, create a .env file at the root of your project.\nAdd "SCOUT9_API_KEY=<your-scout9-api-key>" to the .env file.\n\n\t> You can get your API key at https://scout9.com\n\n`);
367
+ } else {
368
+ throw new Error(`Missing "SCOUT9_API_KEY" within .env file.\n\n\tTo fix, add "SCOUT9_API_KEY=<your-scout9-api-key>" to the .env file.\n\n\tYou can get your API key at https://scout9.com\n\n`);
369
+ }
370
+ }
371
+ }
372
+
373
+ const {pkg} = await loadUserPackageJson({cwd: this.cwd});
374
+ const tag = `${pkg.name || 'scout9-app'}-v${pkg.version || '0.0.1'}`;
375
+ const {mod, filePath} = await this.root.load();
376
+ const root = mod?.default;
377
+ if (!root || checkVariableType(root) !== 'json object') {
378
+ throw new Error(`Invalid root project file ${filePath} - must be an object, example:\n\nexport default {/** ... */}`);
379
+ }
380
+ return {
381
+ tag,
382
+ ...root
383
+ };
384
+ }
385
+
386
+
387
+ }
@@ -0,0 +1,29 @@
1
+ import { fileTypeFromBuffer } from './file-type.js';
2
+
3
+ export const videoExtensions = new Set([
4
+ 'mp4',
5
+ 'm4v',
6
+ 'mkv',
7
+ 'webm',
8
+ 'mov',
9
+ 'avi',
10
+ 'wmv',
11
+ 'mpg',
12
+ 'flv',
13
+ ]);
14
+
15
+ /**
16
+ * @typedef {Object} VideoTypeResult
17
+ * @property {string} ext - One of the supported [file types](https://github.com/sindresorhus/image-type#supported-file-types).
18
+ * @property {string} mime - The detected [MIME type](https://en.wikipedia.org/wiki/Internet_media_type).
19
+ */
20
+
21
+ /**
22
+ *
23
+ * @param {Buffer | Uint8Array} input
24
+ * @returns {Promise<VideoTypeResult | undefined>}
25
+ */
26
+ export default async function videoType(input) {
27
+ const result = await fileTypeFromBuffer(input);
28
+ return videoExtensions.has(result?.ext) && result;
29
+ }