@friggframework/ai-agents 2.0.0--canary.522.cbd3d5a.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 (33) hide show
  1. package/LICENSE.md +9 -0
  2. package/jest.config.js +8 -0
  3. package/package.json +60 -0
  4. package/src/domain/entities/agent-event.js +55 -0
  5. package/src/domain/entities/agent-proposal.js +81 -0
  6. package/src/domain/entities/index.js +9 -0
  7. package/src/domain/index.js +7 -0
  8. package/src/domain/interfaces/agent-framework.js +29 -0
  9. package/src/domain/interfaces/index.js +10 -0
  10. package/src/domain/interfaces/validation-pipeline.js +61 -0
  11. package/src/index.js +41 -0
  12. package/src/infrastructure/adapters/claude-agent-adapter.js +148 -0
  13. package/src/infrastructure/adapters/index.js +10 -0
  14. package/src/infrastructure/adapters/vercel-ai-adapter.js +135 -0
  15. package/src/infrastructure/git/git-checkpoint-service.js +110 -0
  16. package/src/infrastructure/git/index.js +3 -0
  17. package/src/infrastructure/mcp/frigg-tools.js +1918 -0
  18. package/src/infrastructure/mcp/index.js +25 -0
  19. package/src/infrastructure/streaming/agent-stream-handler.js +117 -0
  20. package/src/infrastructure/streaming/index.js +3 -0
  21. package/src/infrastructure/validation/index.js +3 -0
  22. package/src/infrastructure/validation/validation-pipeline.js +226 -0
  23. package/tests/integration/agent-workflow.test.js +276 -0
  24. package/tests/unit/domain/entities/agent-event.test.js +73 -0
  25. package/tests/unit/domain/entities/agent-proposal.test.js +121 -0
  26. package/tests/unit/domain/interfaces/agent-framework.test.js +66 -0
  27. package/tests/unit/domain/interfaces/validation-pipeline.test.js +83 -0
  28. package/tests/unit/infrastructure/adapters/claude-agent-adapter.test.js +193 -0
  29. package/tests/unit/infrastructure/adapters/vercel-ai-adapter.test.js +174 -0
  30. package/tests/unit/infrastructure/git/git-checkpoint-service.test.js +182 -0
  31. package/tests/unit/infrastructure/mcp/frigg-tools.test.js +810 -0
  32. package/tests/unit/infrastructure/streaming/agent-stream-handler.test.js +214 -0
  33. package/tests/unit/infrastructure/validation/validation-pipeline.test.js +288 -0
@@ -0,0 +1,1918 @@
1
+ const { exec, spawn } = require('child_process');
2
+ const { promisify } = require('util');
3
+ const path = require('path');
4
+ const fs = require('fs').promises;
5
+ const axios = require('axios');
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ const INTEGRATION_CATEGORIES = [
10
+ 'CRM', 'Marketing', 'Communication', 'ECommerce',
11
+ 'Finance', 'Analytics', 'Storage', 'Development',
12
+ 'Productivity', 'Social', 'Other'
13
+ ];
14
+
15
+ const INTEGRATION_TYPES = ['api', 'webhook', 'sync', 'transform', 'custom'];
16
+
17
+ const CATEGORY_TEMPLATES = {
18
+ CRM: (name, options = {}) => `const { IntegrationBase } = require('@friggframework/core');
19
+
20
+ class ${capitalize(name)}Integration extends IntegrationBase {
21
+ static Definition = {
22
+ name: '${name.toLowerCase()}',
23
+ version: '1.0.0',
24
+ modules: {
25
+ ${name.toLowerCase()}: { definition: require('@friggframework/api-module-${name.toLowerCase()}') }
26
+ },
27
+ options: {
28
+ type: 'api',
29
+ hasUserConfig: true,
30
+ display: {
31
+ name: '${capitalize(name)}',
32
+ description: '${capitalize(name)} CRM integration for contacts, deals, and company management',
33
+ category: 'CRM',
34
+ icon: '${name.toLowerCase()}'
35
+ }
36
+ },
37
+ capabilities: {
38
+ auth: ['oauth2'],
39
+ webhooks: ${options.webhooks || false},
40
+ sync: { bidirectional: true, incremental: true }
41
+ }
42
+ };
43
+
44
+ async onCreate({ integrationId }) {
45
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
46
+ ${options.webhooks ? `// Register webhooks for real-time updates
47
+ // await this.registerWebhooks();` : ''}
48
+ }
49
+
50
+ async onUpdate(params) {
51
+ await this.validateConfig();
52
+ }
53
+
54
+ async onDelete(params) {
55
+ ${options.webhooks ? `// Cleanup webhooks
56
+ // await this.unregisterWebhooks();` : ''}
57
+ }
58
+
59
+ async getConfigOptions() {
60
+ return {
61
+ jsonSchema: {
62
+ type: 'object',
63
+ properties: {
64
+ syncContacts: { type: 'boolean', title: 'Sync Contacts', default: true },
65
+ syncDeals: { type: 'boolean', title: 'Sync Deals', default: true },
66
+ syncCompanies: { type: 'boolean', title: 'Sync Companies', default: true }
67
+ }
68
+ },
69
+ uiSchema: {}
70
+ };
71
+ }
72
+
73
+ async testAuth() {
74
+ const module = this.getModule('${name.toLowerCase()}');
75
+ return module.testAuth();
76
+ }
77
+ ${options.webhooks ? `
78
+ async onWebhookReceived({ req, res }) {
79
+ await this.queueWebhook({
80
+ integrationId: req.params.integrationId,
81
+ body: req.body,
82
+ headers: req.headers
83
+ });
84
+ res.status(200).json({ received: true });
85
+ }
86
+
87
+ async onWebhook({ data }) {
88
+ const { body } = data;
89
+ // Process CRM webhook events (contact.created, deal.updated, etc.)
90
+ }
91
+ ` : ''}}
92
+
93
+ module.exports = { ${capitalize(name)}Integration };
94
+ `,
95
+
96
+ Finance: (name, options = {}) => `const { IntegrationBase } = require('@friggframework/core');
97
+
98
+ class ${capitalize(name)}Integration extends IntegrationBase {
99
+ static Definition = {
100
+ name: '${name.toLowerCase()}',
101
+ version: '1.0.0',
102
+ modules: {
103
+ ${name.toLowerCase()}: { definition: require('@friggframework/api-module-${name.toLowerCase()}') }
104
+ },
105
+ options: {
106
+ type: 'api',
107
+ hasUserConfig: true,
108
+ display: {
109
+ name: '${capitalize(name)}',
110
+ description: '${capitalize(name)} finance integration for invoices, payments, and accounting',
111
+ category: 'Finance',
112
+ icon: '${name.toLowerCase()}'
113
+ }
114
+ },
115
+ capabilities: {
116
+ auth: ['${options.authType || 'oauth2'}'],
117
+ webhooks: ${options.webhooks || false},
118
+ sync: { bidirectional: false, incremental: true }
119
+ }
120
+ };
121
+
122
+ async onCreate({ integrationId }) {
123
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
124
+ }
125
+
126
+ async onUpdate(params) {
127
+ await this.validateConfig();
128
+ }
129
+
130
+ async onDelete(params) {}
131
+
132
+ async getConfigOptions() {
133
+ return {
134
+ jsonSchema: {
135
+ type: 'object',
136
+ properties: {
137
+ syncInvoices: { type: 'boolean', title: 'Sync Invoices', default: true },
138
+ syncPayments: { type: 'boolean', title: 'Sync Payments', default: true },
139
+ syncCustomers: { type: 'boolean', title: 'Sync Customers', default: true }
140
+ }
141
+ },
142
+ uiSchema: {}
143
+ };
144
+ }
145
+
146
+ async testAuth() {
147
+ const module = this.getModule('${name.toLowerCase()}');
148
+ return module.testAuth();
149
+ }
150
+ }
151
+
152
+ module.exports = { ${capitalize(name)}Integration };
153
+ `,
154
+
155
+ Communication: (name, options = {}) => `const { IntegrationBase } = require('@friggframework/core');
156
+
157
+ class ${capitalize(name)}Integration extends IntegrationBase {
158
+ static Definition = {
159
+ name: '${name.toLowerCase()}',
160
+ version: '1.0.0',
161
+ modules: {
162
+ ${name.toLowerCase()}: { definition: require('@friggframework/api-module-${name.toLowerCase()}') }
163
+ },
164
+ options: {
165
+ type: 'api',
166
+ hasUserConfig: true,
167
+ display: {
168
+ name: '${capitalize(name)}',
169
+ description: '${capitalize(name)} communication integration for messaging and notifications',
170
+ category: 'Communication',
171
+ icon: '${name.toLowerCase()}'
172
+ }
173
+ },
174
+ capabilities: {
175
+ auth: ['oauth2'],
176
+ webhooks: true,
177
+ realtime: true
178
+ }
179
+ };
180
+
181
+ async onCreate({ integrationId }) {
182
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
183
+ // Subscribe to message events
184
+ }
185
+
186
+ async onUpdate(params) {
187
+ await this.validateConfig();
188
+ }
189
+
190
+ async onDelete(params) {
191
+ // Unsubscribe from events
192
+ }
193
+
194
+ async getConfigOptions() {
195
+ return {
196
+ jsonSchema: {
197
+ type: 'object',
198
+ properties: {
199
+ defaultChannel: { type: 'string', title: 'Default Channel' },
200
+ notifyOnMention: { type: 'boolean', title: 'Notify on Mention', default: true }
201
+ }
202
+ },
203
+ uiSchema: {}
204
+ };
205
+ }
206
+
207
+ async testAuth() {
208
+ const module = this.getModule('${name.toLowerCase()}');
209
+ return module.testAuth();
210
+ }
211
+
212
+ async onWebhookReceived({ req, res }) {
213
+ // Handle Slack/Teams challenge verification
214
+ if (req.body.challenge) {
215
+ return res.status(200).json({ challenge: req.body.challenge });
216
+ }
217
+
218
+ await this.queueWebhook({
219
+ integrationId: req.params.integrationId,
220
+ body: req.body,
221
+ headers: req.headers
222
+ });
223
+ res.status(200).json({ received: true });
224
+ }
225
+
226
+ async onWebhook({ data }) {
227
+ const { body } = data;
228
+ // Process message events
229
+ }
230
+ }
231
+
232
+ module.exports = { ${capitalize(name)}Integration };
233
+ `,
234
+
235
+ ECommerce: (name, options = {}) => `const { IntegrationBase } = require('@friggframework/core');
236
+
237
+ class ${capitalize(name)}Integration extends IntegrationBase {
238
+ static Definition = {
239
+ name: '${name.toLowerCase()}',
240
+ version: '1.0.0',
241
+ modules: {
242
+ ${name.toLowerCase()}: { definition: require('@friggframework/api-module-${name.toLowerCase()}') }
243
+ },
244
+ options: {
245
+ type: 'api',
246
+ hasUserConfig: true,
247
+ display: {
248
+ name: '${capitalize(name)}',
249
+ description: '${capitalize(name)} e-commerce integration for orders, products, and customers',
250
+ category: 'ECommerce',
251
+ icon: '${name.toLowerCase()}'
252
+ }
253
+ },
254
+ capabilities: {
255
+ auth: ['oauth2'],
256
+ webhooks: true,
257
+ sync: { bidirectional: true, incremental: true, batchSize: 250 }
258
+ }
259
+ };
260
+
261
+ async onCreate({ integrationId }) {
262
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
263
+ // Register order and inventory webhooks
264
+ }
265
+
266
+ async onUpdate(params) {
267
+ await this.validateConfig();
268
+ }
269
+
270
+ async onDelete(params) {
271
+ // Cleanup webhooks
272
+ }
273
+
274
+ async getConfigOptions() {
275
+ return {
276
+ jsonSchema: {
277
+ type: 'object',
278
+ properties: {
279
+ syncOrders: { type: 'boolean', title: 'Sync Orders', default: true },
280
+ syncProducts: { type: 'boolean', title: 'Sync Products', default: true },
281
+ syncCustomers: { type: 'boolean', title: 'Sync Customers', default: true },
282
+ syncInventory: { type: 'boolean', title: 'Sync Inventory', default: false }
283
+ }
284
+ },
285
+ uiSchema: {}
286
+ };
287
+ }
288
+
289
+ async testAuth() {
290
+ const module = this.getModule('${name.toLowerCase()}');
291
+ return module.testAuth();
292
+ }
293
+
294
+ async onWebhookReceived({ req, res }) {
295
+ // Verify webhook signature
296
+ await this.queueWebhook({
297
+ integrationId: req.params.integrationId,
298
+ body: req.body,
299
+ headers: req.headers
300
+ });
301
+ res.status(200).json({ received: true });
302
+ }
303
+
304
+ async onWebhook({ data }) {
305
+ const { body } = data;
306
+ // Process order/product/inventory events
307
+ }
308
+ }
309
+
310
+ module.exports = { ${capitalize(name)}Integration };
311
+ `,
312
+
313
+ Storage: (name, options = {}) => `const { IntegrationBase } = require('@friggframework/core');
314
+
315
+ class ${capitalize(name)}Integration extends IntegrationBase {
316
+ static Definition = {
317
+ name: '${name.toLowerCase()}',
318
+ version: '1.0.0',
319
+ modules: {
320
+ ${name.toLowerCase()}: { definition: require('@friggframework/api-module-${name.toLowerCase()}') }
321
+ },
322
+ options: {
323
+ type: 'api',
324
+ hasUserConfig: true,
325
+ display: {
326
+ name: '${capitalize(name)}',
327
+ description: '${capitalize(name)} storage integration for files and documents',
328
+ category: 'Storage',
329
+ icon: '${name.toLowerCase()}'
330
+ }
331
+ },
332
+ capabilities: {
333
+ auth: ['oauth2'],
334
+ webhooks: ${options.webhooks || false}
335
+ }
336
+ };
337
+
338
+ async onCreate({ integrationId }) {
339
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
340
+ }
341
+
342
+ async onUpdate(params) {
343
+ await this.validateConfig();
344
+ }
345
+
346
+ async onDelete(params) {}
347
+
348
+ async getConfigOptions() {
349
+ return {
350
+ jsonSchema: {
351
+ type: 'object',
352
+ properties: {
353
+ rootFolder: { type: 'string', title: 'Root Folder Path' },
354
+ syncSubfolders: { type: 'boolean', title: 'Sync Subfolders', default: true }
355
+ }
356
+ },
357
+ uiSchema: {}
358
+ };
359
+ }
360
+
361
+ async testAuth() {
362
+ const module = this.getModule('${name.toLowerCase()}');
363
+ return module.testAuth();
364
+ }
365
+ }
366
+
367
+ module.exports = { ${capitalize(name)}Integration };
368
+ `,
369
+
370
+ Webhook: (name, options = {}) => `const { IntegrationBase } = require('@friggframework/core');
371
+ const crypto = require('crypto');
372
+
373
+ class ${capitalize(name)}Integration extends IntegrationBase {
374
+ static Definition = {
375
+ name: '${name.toLowerCase()}',
376
+ version: '1.0.0',
377
+ modules: {},
378
+ options: {
379
+ type: 'webhook',
380
+ hasUserConfig: true,
381
+ display: {
382
+ name: '${capitalize(name)}',
383
+ description: '${capitalize(name)} webhook-only integration for receiving external events',
384
+ category: '${options.category || 'Other'}',
385
+ icon: '${name.toLowerCase()}'
386
+ }
387
+ },
388
+ capabilities: {
389
+ auth: ['custom'],
390
+ webhooks: true
391
+ }
392
+ };
393
+
394
+ async onCreate({ integrationId }) {
395
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
396
+ }
397
+
398
+ async onUpdate(params) {}
399
+
400
+ async onDelete(params) {}
401
+
402
+ async getConfigOptions() {
403
+ return {
404
+ jsonSchema: {
405
+ type: 'object',
406
+ required: ['webhookSecret'],
407
+ properties: {
408
+ webhookSecret: {
409
+ type: 'string',
410
+ title: 'Webhook Secret',
411
+ description: 'Secret for validating webhook signatures'
412
+ }
413
+ }
414
+ },
415
+ uiSchema: {
416
+ webhookSecret: { 'ui:widget': 'password' }
417
+ }
418
+ };
419
+ }
420
+
421
+ async testAuth() {
422
+ return true;
423
+ }
424
+
425
+ verifySignature(body, signature, secret) {
426
+ const expected = crypto
427
+ .createHmac('sha256', secret)
428
+ .update(JSON.stringify(body))
429
+ .digest('hex');
430
+ return crypto.timingSafeEqual(
431
+ Buffer.from(signature || ''),
432
+ Buffer.from(expected)
433
+ );
434
+ }
435
+
436
+ async onWebhookReceived({ req, res }) {
437
+ const signature = req.headers['x-webhook-signature'] || req.headers['x-hub-signature-256'];
438
+ const config = this.getConfig();
439
+
440
+ if (config.webhookSecret && !this.verifySignature(req.body, signature, config.webhookSecret)) {
441
+ return res.status(401).json({ error: 'Invalid signature' });
442
+ }
443
+
444
+ await this.queueWebhook({
445
+ integrationId: req.params.integrationId,
446
+ body: req.body,
447
+ headers: req.headers
448
+ });
449
+ res.status(200).json({ received: true });
450
+ }
451
+
452
+ async onWebhook({ data }) {
453
+ const { body } = data;
454
+ // Process webhook event
455
+ }
456
+ }
457
+
458
+ module.exports = { ${capitalize(name)}Integration };
459
+ `,
460
+
461
+ Sync: (name, options = {}) => `const { IntegrationBase } = require('@friggframework/core');
462
+
463
+ class ${capitalize(name)}Integration extends IntegrationBase {
464
+ static Definition = {
465
+ name: '${name.toLowerCase()}',
466
+ version: '1.0.0',
467
+ modules: {
468
+ source: { definition: require('@friggframework/api-module-${options.sourceModule || name.toLowerCase()}') },
469
+ target: { definition: require('@friggframework/api-module-${options.targetModule || name.toLowerCase()}') }
470
+ },
471
+ options: {
472
+ type: 'sync',
473
+ hasUserConfig: true,
474
+ display: {
475
+ name: '${capitalize(name)}',
476
+ description: '${capitalize(name)} sync integration for bidirectional data synchronization',
477
+ category: '${options.category || 'Other'}',
478
+ icon: '${name.toLowerCase()}'
479
+ }
480
+ },
481
+ capabilities: {
482
+ auth: ['oauth2'],
483
+ webhooks: true,
484
+ sync: { bidirectional: true, incremental: true, batchSize: 100 }
485
+ }
486
+ };
487
+
488
+ async onCreate({ integrationId }) {
489
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
490
+ // Initialize sync state
491
+ }
492
+
493
+ async onUpdate(params) {
494
+ await this.validateConfig();
495
+ }
496
+
497
+ async onDelete(params) {
498
+ // Cleanup sync state
499
+ }
500
+
501
+ async getConfigOptions() {
502
+ return {
503
+ jsonSchema: {
504
+ type: 'object',
505
+ properties: {
506
+ syncDirection: {
507
+ type: 'string',
508
+ title: 'Sync Direction',
509
+ enum: ['source-to-target', 'target-to-source', 'bidirectional'],
510
+ default: 'bidirectional'
511
+ },
512
+ conflictResolution: {
513
+ type: 'string',
514
+ title: 'Conflict Resolution',
515
+ enum: ['source-wins', 'target-wins', 'newest-wins'],
516
+ default: 'newest-wins'
517
+ },
518
+ syncInterval: {
519
+ type: 'integer',
520
+ title: 'Sync Interval (minutes)',
521
+ default: 15,
522
+ minimum: 5
523
+ }
524
+ }
525
+ },
526
+ uiSchema: {}
527
+ };
528
+ }
529
+
530
+ async testAuth() {
531
+ const sourceModule = this.getModule('source');
532
+ const targetModule = this.getModule('target');
533
+ await sourceModule.testAuth();
534
+ await targetModule.testAuth();
535
+ return true;
536
+ }
537
+ }
538
+
539
+ module.exports = { ${capitalize(name)}Integration };
540
+ `
541
+ };
542
+
543
+ function capitalize(str) {
544
+ return str.charAt(0).toUpperCase() + str.slice(1);
545
+ }
546
+
547
+ class NPMRegistryService {
548
+ constructor() {
549
+ this.searchUrl = 'https://registry.npmjs.org/-/v1/search';
550
+ this.packageScope = '@friggframework';
551
+ this.modulePrefix = 'api-module-';
552
+ }
553
+
554
+ async searchApiModules(options = {}) {
555
+ const { category, limit = 250 } = options;
556
+ const searchQuery = `${this.packageScope}/${this.modulePrefix}`;
557
+
558
+ try {
559
+ const response = await axios.get(this.searchUrl, {
560
+ params: {
561
+ text: searchQuery,
562
+ size: limit,
563
+ quality: 0.65,
564
+ popularity: 0.98,
565
+ maintenance: 0.5
566
+ },
567
+ timeout: 10000
568
+ });
569
+
570
+ let modules = response.data.objects
571
+ .filter(obj => obj.package.name.startsWith(`${this.packageScope}/${this.modulePrefix}`))
572
+ .map(obj => this.formatPackageInfo(obj.package));
573
+
574
+ if (category && category !== 'all') {
575
+ modules = modules.filter(m => m.category === category);
576
+ }
577
+
578
+ return modules;
579
+ } catch (error) {
580
+ return [];
581
+ }
582
+ }
583
+
584
+ formatPackageInfo(pkg) {
585
+ const name = pkg.name.replace(`${this.packageScope}/${this.modulePrefix}`, '');
586
+ return {
587
+ name,
588
+ fullName: pkg.name,
589
+ displayName: this.formatDisplayName(name),
590
+ version: pkg.version,
591
+ description: pkg.description || '',
592
+ category: this.categorizeModule(name, pkg.description || ''),
593
+ authType: this.inferAuthType(name, pkg.description || '')
594
+ };
595
+ }
596
+
597
+ formatDisplayName(name) {
598
+ return name
599
+ .split('-')
600
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
601
+ .join(' ');
602
+ }
603
+
604
+ categorizeModule(name, description) {
605
+ const text = `${name} ${description}`.toLowerCase();
606
+ const categories = {
607
+ 'CRM': ['crm', 'customer', 'salesforce', 'hubspot', 'pipedrive', 'zoho', 'attio', 'copper'],
608
+ 'Finance': ['accounting', 'quickbooks', 'xero', 'sage', 'invoice', 'billing', 'stripe', 'payment'],
609
+ 'Communication': ['email', 'sms', 'chat', 'messaging', 'slack', 'discord', 'twilio', 'teams', 'intercom'],
610
+ 'ECommerce': ['shop', 'commerce', 'shopify', 'woocommerce', 'magento', 'bigcommerce', 'store'],
611
+ 'Marketing': ['marketing', 'campaign', 'mailchimp', 'sendgrid', 'marketo', 'constantcontact'],
612
+ 'Analytics': ['analytics', 'tracking', 'mixpanel', 'segment', 'amplitude', 'google-analytics'],
613
+ 'Storage': ['storage', 'drive', 'dropbox', 'box', 'onedrive', 'file', 'document'],
614
+ 'Development': ['github', 'gitlab', 'bitbucket', 'jira', 'linear', 'notion', 'confluence'],
615
+ 'Productivity': ['asana', 'monday', 'trello', 'clickup', 'basecamp', 'todoist', 'airtable'],
616
+ 'Social': ['social', 'twitter', 'facebook', 'linkedin', 'instagram', 'youtube']
617
+ };
618
+
619
+ for (const [category, keywords] of Object.entries(categories)) {
620
+ if (keywords.some(keyword => text.includes(keyword))) {
621
+ return category;
622
+ }
623
+ }
624
+ return 'Other';
625
+ }
626
+
627
+ inferAuthType(name, description) {
628
+ const text = `${name} ${description}`.toLowerCase();
629
+ if (text.includes('api key') || text.includes('apikey')) {
630
+ return 'api-key';
631
+ }
632
+ return 'oauth2';
633
+ }
634
+ }
635
+
636
+ let gitCheckpointServiceInstance = null;
637
+
638
+ function setGitCheckpointService(service) {
639
+ gitCheckpointServiceInstance = service;
640
+ }
641
+
642
+ async function validateSchemaHandler({ schemaType, content }) {
643
+ let parsed;
644
+ try {
645
+ parsed = typeof content === 'string' ? JSON.parse(content) : content;
646
+ } catch (e) {
647
+ return { valid: false, errors: [`Invalid JSON: ${e.message}`] };
648
+ }
649
+
650
+ const errors = [];
651
+ const warnings = [];
652
+
653
+ if (schemaType === 'integration-definition') {
654
+ if (!parsed.name) {
655
+ errors.push('name is required');
656
+ } else if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(parsed.name)) {
657
+ errors.push('name must match pattern ^[a-zA-Z][a-zA-Z0-9_-]*$');
658
+ }
659
+
660
+ if (!parsed.version) {
661
+ errors.push('version is required');
662
+ } else if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(parsed.version)) {
663
+ errors.push('version must follow semantic versioning (X.Y.Z or X.Y.Z-prerelease)');
664
+ }
665
+
666
+ if (parsed.options?.type && !INTEGRATION_TYPES.includes(parsed.options.type)) {
667
+ errors.push(`options.type must be one of: ${INTEGRATION_TYPES.join(', ')}`);
668
+ }
669
+
670
+ if (parsed.options?.display?.category && !INTEGRATION_CATEGORIES.includes(parsed.options.display.category)) {
671
+ errors.push(`options.display.category must be one of: ${INTEGRATION_CATEGORIES.join(', ')}`);
672
+ }
673
+
674
+ if (parsed.capabilities?.auth) {
675
+ const validAuth = ['oauth2', 'api-key', 'basic', 'token', 'custom'];
676
+ for (const auth of parsed.capabilities.auth) {
677
+ if (!validAuth.includes(auth)) {
678
+ errors.push(`capabilities.auth contains invalid value: ${auth}`);
679
+ }
680
+ }
681
+ }
682
+
683
+ if (parsed.model?.status) {
684
+ const validStatus = ['active', 'inactive', 'error', 'pending', 'disabled'];
685
+ if (!validStatus.includes(parsed.model.status)) {
686
+ errors.push(`model.status must be one of: ${validStatus.join(', ')}`);
687
+ }
688
+ }
689
+
690
+ if (!parsed.modules || Object.keys(parsed.modules).length === 0) {
691
+ warnings.push('No modules defined - integration may not connect to external services');
692
+ }
693
+
694
+ if (!parsed.options?.display?.name) {
695
+ warnings.push('Missing display.name - UI will use internal name');
696
+ }
697
+ }
698
+
699
+ if (schemaType === 'api-module-definition') {
700
+ if (!parsed.name && !parsed.moduleName) {
701
+ errors.push('name or moduleName is required');
702
+ }
703
+
704
+ if (!parsed.authType && !parsed.requester?.baseUrl) {
705
+ warnings.push('No authType or baseUrl specified');
706
+ }
707
+ }
708
+
709
+ if (schemaType === 'app-definition') {
710
+ if (!parsed.name) {
711
+ errors.push('name is required');
712
+ }
713
+
714
+ if (!parsed.integrations || parsed.integrations.length === 0) {
715
+ warnings.push('No integrations defined in app');
716
+ }
717
+ }
718
+
719
+ return {
720
+ valid: errors.length === 0,
721
+ errors,
722
+ warnings: warnings.length > 0 ? warnings : undefined
723
+ };
724
+ }
725
+
726
+ async function getTemplateHandler({ category, integrationName, options = {} }) {
727
+ const templateFn = CATEGORY_TEMPLATES[category];
728
+
729
+ if (!templateFn) {
730
+ const availableCategories = Object.keys(CATEGORY_TEMPLATES);
731
+ return {
732
+ error: `Unknown category: ${category}. Available: ${availableCategories.join(', ')}`,
733
+ availableCategories
734
+ };
735
+ }
736
+
737
+ const template = templateFn(integrationName || 'MyIntegration', options);
738
+ return {
739
+ template: template.trim(),
740
+ category,
741
+ integrationName: integrationName || 'MyIntegration',
742
+ suggestedFilename: `${(integrationName || 'my-integration').toLowerCase()}-integration.js`
743
+ };
744
+ }
745
+
746
+ async function checkPatternsHandler({ code, fileType }) {
747
+ const violations = [];
748
+ const suggestions = [];
749
+
750
+ if (fileType === 'integration') {
751
+ if (!code.includes('extends IntegrationBase')) {
752
+ violations.push({
753
+ rule: 'extends-integration-base',
754
+ severity: 'error',
755
+ message: 'Integration must extend IntegrationBase',
756
+ suggestion: 'class YourIntegration extends IntegrationBase { ... }'
757
+ });
758
+ }
759
+
760
+ if (!code.includes('static Definition')) {
761
+ violations.push({
762
+ rule: 'static-definition',
763
+ severity: 'error',
764
+ message: 'Integration must have static Definition property',
765
+ suggestion: 'Add: static Definition = { name, version, modules, options, capabilities }'
766
+ });
767
+ } else {
768
+ if (!code.includes("name:") && !code.includes('name :')) {
769
+ violations.push({
770
+ rule: 'definition-name',
771
+ severity: 'error',
772
+ message: 'Definition must include name property'
773
+ });
774
+ }
775
+ if (!code.includes("version:") && !code.includes('version :')) {
776
+ violations.push({
777
+ rule: 'definition-version',
778
+ severity: 'error',
779
+ message: 'Definition must include version property'
780
+ });
781
+ }
782
+ }
783
+
784
+ const lifecycleMethods = ['onCreate', 'onUpdate', 'onDelete', 'getConfigOptions', 'testAuth'];
785
+ const missingMethods = lifecycleMethods.filter(m => !code.includes(`async ${m}(`));
786
+
787
+ if (missingMethods.length > 0) {
788
+ violations.push({
789
+ rule: 'lifecycle-methods',
790
+ severity: 'warning',
791
+ message: `Missing lifecycle methods: ${missingMethods.join(', ')}`,
792
+ suggestion: `Consider implementing: ${missingMethods.map(m => `async ${m}() { }`).join(', ')}`
793
+ });
794
+ }
795
+
796
+ if (code.includes('webhooks: true') || code.includes("type: 'webhook'")) {
797
+ if (!code.includes('onWebhookReceived') || !code.includes('onWebhook')) {
798
+ violations.push({
799
+ rule: 'webhook-handlers',
800
+ severity: 'warning',
801
+ message: 'Webhooks enabled but handlers not implemented',
802
+ suggestion: 'Implement onWebhookReceived() and onWebhook() methods'
803
+ });
804
+ }
805
+ }
806
+
807
+ if (!code.includes('updateIntegrationStatus')) {
808
+ suggestions.push({
809
+ rule: 'status-updates',
810
+ message: 'Consider calling updateIntegrationStatus in onCreate',
811
+ suggestion: "await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');"
812
+ });
813
+ }
814
+ }
815
+
816
+ if (fileType === 'api-module') {
817
+ if (!code.includes('class') || (!code.includes('extends') && !code.includes('Api'))) {
818
+ violations.push({
819
+ rule: 'api-class',
820
+ severity: 'warning',
821
+ message: 'API module should define a class (typically extending a base Api class)'
822
+ });
823
+ }
824
+
825
+ if (!code.includes('testAuth')) {
826
+ violations.push({
827
+ rule: 'test-auth',
828
+ severity: 'warning',
829
+ message: 'API module should implement testAuth() method'
830
+ });
831
+ }
832
+ }
833
+
834
+ return {
835
+ compliant: violations.filter(v => v.severity === 'error').length === 0,
836
+ violations,
837
+ suggestions: suggestions.length > 0 ? suggestions : undefined
838
+ };
839
+ }
840
+
841
+ async function listModulesHandler({ category }) {
842
+ const npmService = new NPMRegistryService();
843
+
844
+ try {
845
+ const modules = await npmService.searchApiModules({ category });
846
+ return {
847
+ modules,
848
+ total: modules.length,
849
+ source: 'npm-registry'
850
+ };
851
+ } catch (error) {
852
+ return {
853
+ modules: [],
854
+ total: 0,
855
+ error: error.message,
856
+ source: 'npm-registry'
857
+ };
858
+ }
859
+ }
860
+
861
+ async function runTestsHandler({ testPattern, coverage = false, watch = false }) {
862
+ const args = ['jest'];
863
+
864
+ if (testPattern) {
865
+ args.push(testPattern);
866
+ }
867
+
868
+ if (coverage) {
869
+ args.push('--coverage');
870
+ }
871
+
872
+ if (watch) {
873
+ args.push('--watch');
874
+ }
875
+
876
+ args.push('--passWithNoTests');
877
+
878
+ return new Promise((resolve) => {
879
+ const jestProcess = spawn('npx', args, {
880
+ cwd: process.cwd(),
881
+ env: { ...process.env, FORCE_COLOR: '0' }
882
+ });
883
+
884
+ let stdout = '';
885
+ let stderr = '';
886
+
887
+ jestProcess.stdout.on('data', (data) => {
888
+ stdout += data.toString();
889
+ });
890
+
891
+ jestProcess.stderr.on('data', (data) => {
892
+ stderr += data.toString();
893
+ });
894
+
895
+ jestProcess.on('close', (code) => {
896
+ const lines = (stdout + stderr).split('\n');
897
+ const summaryLine = lines.find(l => l.includes('Tests:') || l.includes('Test Suites:'));
898
+ const coverageLine = lines.find(l => l.includes('Coverage'));
899
+
900
+ let passed = 0;
901
+ let failed = 0;
902
+ let total = 0;
903
+
904
+ const testMatch = (stdout + stderr).match(/Tests:\s+(\d+)\s+passed/);
905
+ const failMatch = (stdout + stderr).match(/(\d+)\s+failed/);
906
+ const totalMatch = (stdout + stderr).match(/(\d+)\s+total/);
907
+
908
+ if (testMatch) passed = parseInt(testMatch[1]);
909
+ if (failMatch) failed = parseInt(failMatch[1]);
910
+ if (totalMatch) total = parseInt(totalMatch[1]);
911
+
912
+ resolve({
913
+ passed,
914
+ failed,
915
+ total,
916
+ exitCode: code,
917
+ success: code === 0,
918
+ summary: summaryLine || 'Tests completed',
919
+ coverage: coverageLine || (coverage ? 'Coverage data in ./coverage' : undefined),
920
+ output: stdout.substring(0, 5000)
921
+ });
922
+ });
923
+
924
+ jestProcess.on('error', (error) => {
925
+ resolve({
926
+ passed: 0,
927
+ failed: 0,
928
+ total: 0,
929
+ exitCode: 1,
930
+ success: false,
931
+ error: error.message
932
+ });
933
+ });
934
+ });
935
+ }
936
+
937
+ async function securityScanHandler({ code, scanType = 'full' }) {
938
+ const vulnerabilities = [];
939
+
940
+ if (scanType === 'full' || scanType === 'credentials') {
941
+ const credentialPatterns = [
942
+ { pattern: /api[_-]?key\s*[:=]\s*['"][^'"]{10,}['"]/gi, type: 'API key' },
943
+ { pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi, type: 'Password' },
944
+ { pattern: /secret\s*[:=]\s*['"][^'"]{10,}['"]/gi, type: 'Secret' },
945
+ { pattern: /token\s*[:=]\s*['"][^'"]{20,}['"]/gi, type: 'Token' },
946
+ { pattern: /bearer\s+[a-zA-Z0-9._-]{20,}/gi, type: 'Bearer token' },
947
+ { pattern: /aws[_-]?(access[_-]?key|secret)[_-]?id?\s*[:=]\s*['"][^'"]+['"]/gi, type: 'AWS credentials' }
948
+ ];
949
+
950
+ for (const { pattern, type } of credentialPatterns) {
951
+ if (pattern.test(code)) {
952
+ vulnerabilities.push({
953
+ type: 'hardcoded-credential',
954
+ credentialType: type,
955
+ severity: 'high',
956
+ description: `Possible hardcoded ${type} detected`,
957
+ fix: 'Use environment variables: process.env.YOUR_SECRET_NAME'
958
+ });
959
+ }
960
+ }
961
+ }
962
+
963
+ if (scanType === 'full' || scanType === 'injection') {
964
+ if (/eval\s*\(/.test(code)) {
965
+ vulnerabilities.push({
966
+ type: 'code-injection',
967
+ severity: 'critical',
968
+ description: 'Use of eval() detected - potential code injection vulnerability',
969
+ fix: 'Avoid eval(). Use JSON.parse() for JSON, or safer alternatives'
970
+ });
971
+ }
972
+
973
+ if (/new\s+Function\s*\(/.test(code) && code.includes('req.')) {
974
+ vulnerabilities.push({
975
+ type: 'code-injection',
976
+ severity: 'high',
977
+ description: 'Dynamic Function constructor with user input detected',
978
+ fix: 'Avoid constructing functions from user input'
979
+ });
980
+ }
981
+
982
+ if (/\$\{.*req\.(body|query|params)/.test(code) && /exec|spawn/.test(code)) {
983
+ vulnerabilities.push({
984
+ type: 'command-injection',
985
+ severity: 'critical',
986
+ description: 'Potential command injection - user input in shell command',
987
+ fix: 'Sanitize input and use parameterized commands'
988
+ });
989
+ }
990
+ }
991
+
992
+ if (scanType === 'full' || scanType === 'validation') {
993
+ if (code.includes('req.body') && !code.includes('validate') && !code.includes('schema') && !code.includes('joi') && !code.includes('zod')) {
994
+ vulnerabilities.push({
995
+ type: 'missing-validation',
996
+ severity: 'medium',
997
+ description: 'Request body used without apparent validation',
998
+ fix: 'Add input validation using a schema library (Joi, Zod, AJV)'
999
+ });
1000
+ }
1001
+
1002
+ if (/onWebhookReceived|onWebhook/.test(code) && !code.includes('signature') && !code.includes('verify') && !code.includes('hmac')) {
1003
+ vulnerabilities.push({
1004
+ type: 'missing-webhook-validation',
1005
+ severity: 'medium',
1006
+ description: 'Webhook handler without signature verification',
1007
+ fix: 'Implement HMAC signature verification for webhook security'
1008
+ });
1009
+ }
1010
+ }
1011
+
1012
+ return {
1013
+ vulnerabilities,
1014
+ scanned: true,
1015
+ scanType,
1016
+ summary: vulnerabilities.length === 0
1017
+ ? 'No vulnerabilities detected'
1018
+ : `Found ${vulnerabilities.length} potential issue(s)`
1019
+ };
1020
+ }
1021
+
1022
+ async function gitCheckpointHandler({ message }) {
1023
+ if (gitCheckpointServiceInstance) {
1024
+ try {
1025
+ const checkpoint = await gitCheckpointServiceInstance.createCheckpoint(message);
1026
+ return {
1027
+ checkpointId: checkpoint.id,
1028
+ hash: checkpoint.hash,
1029
+ message: checkpoint.message,
1030
+ timestamp: checkpoint.timestamp,
1031
+ hasPendingChanges: checkpoint.hasPendingChanges,
1032
+ rollbackCommand: `git reset --mixed ${checkpoint.hash}`
1033
+ };
1034
+ } catch (error) {
1035
+ return {
1036
+ error: error.message,
1037
+ fallback: true
1038
+ };
1039
+ }
1040
+ }
1041
+
1042
+ try {
1043
+ const { stdout: hash } = await execAsync('git rev-parse HEAD');
1044
+ const { stdout: status } = await execAsync('git status --porcelain');
1045
+
1046
+ const checkpointId = `checkpoint-${Date.now()}-${hash.trim().substring(0, 8)}`;
1047
+
1048
+ return {
1049
+ checkpointId,
1050
+ hash: hash.trim(),
1051
+ message,
1052
+ timestamp: new Date().toISOString(),
1053
+ hasPendingChanges: status.trim().length > 0,
1054
+ rollbackCommand: `git reset --mixed ${hash.trim()}`
1055
+ };
1056
+ } catch (error) {
1057
+ return {
1058
+ error: `Git error: ${error.message}`,
1059
+ suggestion: 'Ensure you are in a git repository'
1060
+ };
1061
+ }
1062
+ }
1063
+
1064
+ async function getExampleHandler({ pattern }) {
1065
+ const examples = {
1066
+ 'crm-integration': {
1067
+ description: 'Complete CRM integration with contacts and deals sync',
1068
+ code: CATEGORY_TEMPLATES.CRM('hubspot', { webhooks: true }),
1069
+ relatedFiles: [
1070
+ 'packages/core/integrations/integration-base.js',
1071
+ 'api-module-library/packages/hubspot/*'
1072
+ ]
1073
+ },
1074
+ 'webhook-handler': {
1075
+ description: 'Secure webhook handler with signature verification',
1076
+ code: `async onWebhookReceived({ req, res }) {
1077
+ const signature = req.headers['x-webhook-signature'];
1078
+ const secret = this.getConfig().webhookSecret;
1079
+
1080
+ if (!this.verifySignature(req.body, signature, secret)) {
1081
+ return res.status(401).json({ error: 'Invalid signature' });
1082
+ }
1083
+
1084
+ await this.queueWebhook({
1085
+ integrationId: req.params.integrationId,
1086
+ body: req.body,
1087
+ headers: req.headers
1088
+ });
1089
+
1090
+ res.status(200).json({ received: true });
1091
+ }
1092
+
1093
+ verifySignature(body, signature, secret) {
1094
+ const crypto = require('crypto');
1095
+ const expected = crypto
1096
+ .createHmac('sha256', secret)
1097
+ .update(JSON.stringify(body))
1098
+ .digest('hex');
1099
+ return crypto.timingSafeEqual(
1100
+ Buffer.from(signature || '', 'utf8'),
1101
+ Buffer.from(expected, 'utf8')
1102
+ );
1103
+ }
1104
+
1105
+ async onWebhook({ data }) {
1106
+ const { body, headers } = data;
1107
+ const eventType = headers['x-event-type'] || body.event;
1108
+
1109
+ switch (eventType) {
1110
+ case 'contact.created':
1111
+ await this.handleContactCreated(body.data);
1112
+ break;
1113
+ case 'deal.updated':
1114
+ await this.handleDealUpdated(body.data);
1115
+ break;
1116
+ default:
1117
+ console.log('Unhandled event:', eventType);
1118
+ }
1119
+ }`
1120
+ },
1121
+ 'form-config': {
1122
+ description: 'Dynamic form configuration with JSON Schema',
1123
+ code: `async getConfigOptions() {
1124
+ const module = this.getModule('myModule');
1125
+ const availableWorkspaces = await module.listWorkspaces();
1126
+
1127
+ return {
1128
+ jsonSchema: {
1129
+ type: 'object',
1130
+ required: ['workspace', 'syncDirection'],
1131
+ properties: {
1132
+ workspace: {
1133
+ type: 'string',
1134
+ title: 'Workspace',
1135
+ enum: availableWorkspaces.map(w => w.id),
1136
+ enumNames: availableWorkspaces.map(w => w.name)
1137
+ },
1138
+ syncDirection: {
1139
+ type: 'string',
1140
+ title: 'Sync Direction',
1141
+ enum: ['push', 'pull', 'bidirectional'],
1142
+ default: 'bidirectional'
1143
+ },
1144
+ syncInterval: {
1145
+ type: 'integer',
1146
+ title: 'Sync Interval (minutes)',
1147
+ minimum: 5,
1148
+ maximum: 1440,
1149
+ default: 60
1150
+ },
1151
+ enableNotifications: {
1152
+ type: 'boolean',
1153
+ title: 'Enable Notifications',
1154
+ default: true
1155
+ }
1156
+ }
1157
+ },
1158
+ uiSchema: {
1159
+ workspace: {
1160
+ 'ui:placeholder': 'Select a workspace...'
1161
+ },
1162
+ syncInterval: {
1163
+ 'ui:widget': 'range'
1164
+ }
1165
+ }
1166
+ };
1167
+ }
1168
+
1169
+ async refreshConfigOptions({ configKey, currentConfig }) {
1170
+ if (configKey === 'workspace') {
1171
+ const module = this.getModule('myModule');
1172
+ const workspaces = await module.listWorkspaces();
1173
+ return {
1174
+ options: workspaces.map(w => ({ value: w.id, label: w.name }))
1175
+ };
1176
+ }
1177
+ return null;
1178
+ }`
1179
+ },
1180
+ 'oauth2-flow': {
1181
+ description: 'OAuth2 authentication flow implementation',
1182
+ code: `// In your API module (not integration)
1183
+ class MyServiceApi extends OAuth2Requester {
1184
+ constructor(params) {
1185
+ super(params);
1186
+ this.baseUrl = 'https://api.myservice.com';
1187
+
1188
+ this.URLs = {
1189
+ authorization: 'https://myservice.com/oauth/authorize',
1190
+ token: 'https://myservice.com/oauth/token',
1191
+ userInfo: '/api/v1/me'
1192
+ };
1193
+ }
1194
+
1195
+ getAuthorizationUri() {
1196
+ return this.authorizationUri({
1197
+ client_id: this.client_id,
1198
+ redirect_uri: this.redirect_uri,
1199
+ scope: 'read write',
1200
+ response_type: 'code'
1201
+ });
1202
+ }
1203
+
1204
+ async getAccessToken(code) {
1205
+ return this._getAccessToken({
1206
+ code,
1207
+ grant_type: 'authorization_code',
1208
+ client_id: this.client_id,
1209
+ client_secret: this.client_secret,
1210
+ redirect_uri: this.redirect_uri
1211
+ });
1212
+ }
1213
+
1214
+ async refreshAccessToken() {
1215
+ return this._refreshAccessToken({
1216
+ grant_type: 'refresh_token',
1217
+ refresh_token: this.refresh_token,
1218
+ client_id: this.client_id,
1219
+ client_secret: this.client_secret
1220
+ });
1221
+ }
1222
+
1223
+ async testAuth() {
1224
+ const response = await this._get(this.URLs.userInfo);
1225
+ return { success: true, user: response };
1226
+ }
1227
+ }`
1228
+ },
1229
+ 'sync-pattern': {
1230
+ description: 'Bidirectional data sync pattern',
1231
+ code: CATEGORY_TEMPLATES.Sync('DataSync', { category: 'Productivity' })
1232
+ },
1233
+ 'api-module-complete': {
1234
+ description: 'Complete API module structure',
1235
+ code: `const { OAuth2Requester } = require('@friggframework/module-plugin');
1236
+ const { Credential } = require('./models/credential');
1237
+ const { Entity } = require('./models/entity');
1238
+
1239
+ class MyServiceApi extends OAuth2Requester {
1240
+ static Config = {
1241
+ name: 'MyService',
1242
+ authType: 'oauth2',
1243
+ hasTestAuth: true
1244
+ };
1245
+
1246
+ constructor(params) {
1247
+ super(params);
1248
+ this.baseUrl = process.env.MYSERVICE_API_URL || 'https://api.myservice.com';
1249
+ this.tokenUri = 'https://myservice.com/oauth/token';
1250
+ this.authorizationUri = 'https://myservice.com/oauth/authorize';
1251
+ }
1252
+
1253
+ // Auth methods
1254
+ getAuthorizationUri() { /* ... */ }
1255
+ async getAccessToken(code) { /* ... */ }
1256
+ async testAuth() {
1257
+ const user = await this._get('/api/v1/me');
1258
+ return { success: true, user };
1259
+ }
1260
+
1261
+ // Resource methods
1262
+ async listContacts(params = {}) {
1263
+ return this._get('/api/v1/contacts', { params });
1264
+ }
1265
+
1266
+ async getContact(id) {
1267
+ return this._get(\`/api/v1/contacts/\${id}\`);
1268
+ }
1269
+
1270
+ async createContact(data) {
1271
+ return this._post('/api/v1/contacts', data);
1272
+ }
1273
+
1274
+ async updateContact(id, data) {
1275
+ return this._patch(\`/api/v1/contacts/\${id}\`, data);
1276
+ }
1277
+
1278
+ async deleteContact(id) {
1279
+ return this._delete(\`/api/v1/contacts/\${id}\`);
1280
+ }
1281
+
1282
+ // Webhook methods
1283
+ async registerWebhook(config) {
1284
+ return this._post('/api/v1/webhooks', config);
1285
+ }
1286
+
1287
+ async deleteWebhook(id) {
1288
+ return this._delete(\`/api/v1/webhooks/\${id}\`);
1289
+ }
1290
+ }
1291
+
1292
+ const Definition = {
1293
+ moduleName: 'myservice',
1294
+ Api: MyServiceApi,
1295
+ Credential,
1296
+ Entity
1297
+ };
1298
+
1299
+ module.exports = { MyServiceApi, Definition };`
1300
+ }
1301
+ };
1302
+
1303
+ const example = examples[pattern];
1304
+
1305
+ if (!example) {
1306
+ return {
1307
+ error: `Unknown pattern: ${pattern}`,
1308
+ availablePatterns: Object.keys(examples).map(key => ({
1309
+ name: key,
1310
+ description: examples[key].description
1311
+ }))
1312
+ };
1313
+ }
1314
+
1315
+ return {
1316
+ pattern,
1317
+ description: example.description,
1318
+ code: example.code.trim(),
1319
+ relatedFiles: example.relatedFiles
1320
+ };
1321
+ }
1322
+
1323
+ const DOCS_INDEX = {
1324
+ 'integration-base': {
1325
+ title: 'IntegrationBase Class',
1326
+ path: 'packages/core/integrations/integration-base.js',
1327
+ topics: ['integration', 'lifecycle', 'modules', 'events', 'webhooks'],
1328
+ summary: 'Base class all integrations must extend. Provides lifecycle methods (onCreate, onUpdate, onDelete), module management, webhook handling, and status updates.'
1329
+ },
1330
+ 'api-module': {
1331
+ title: 'API Module Development',
1332
+ path: 'docs/api-module-library/overview.md',
1333
+ topics: ['api-module', 'oauth2', 'api-key', 'requester'],
1334
+ summary: 'Guide to building API modules that connect to external services. Covers authentication types, requester patterns, and module structure.'
1335
+ },
1336
+ 'forms-config': {
1337
+ title: 'Form Configuration (JSON Schema)',
1338
+ path: 'packages/core/integrations/options.js',
1339
+ topics: ['forms', 'json-schema', 'ui-schema', 'config-options'],
1340
+ summary: 'Dynamic form configuration using JSON Schema. Used in getConfigOptions() to define user-configurable settings.'
1341
+ },
1342
+ 'webhooks': {
1343
+ title: 'Webhook Handling',
1344
+ path: 'packages/core/integrations/integration-base.js',
1345
+ topics: ['webhooks', 'signature', 'queue', 'events'],
1346
+ summary: 'Webhook implementation patterns including signature verification, event queuing, and processing. Methods: onWebhookReceived, onWebhook, queueWebhook.'
1347
+ },
1348
+ 'encryption': {
1349
+ title: 'Field-Level Encryption',
1350
+ path: 'packages/core/database/encryption/README.md',
1351
+ topics: ['encryption', 'kms', 'aes', 'credentials', 'security'],
1352
+ summary: 'Transparent field-level encryption for sensitive data. Supports AWS KMS and AES. Automatically encrypts credentials, tokens, and mapping data.'
1353
+ },
1354
+ 'repositories': {
1355
+ title: 'Repository Pattern',
1356
+ path: 'packages/core/integrations/repositories/',
1357
+ topics: ['repository', 'database', 'crud', 'prisma', 'mongo'],
1358
+ summary: 'Data access layer following repository pattern. Supports MongoDB, PostgreSQL via Prisma, and DocumentDB. Handles integration and mapping persistence.'
1359
+ },
1360
+ 'use-cases': {
1361
+ title: 'Use Case Pattern',
1362
+ path: 'packages/core/integrations/use-cases/',
1363
+ topics: ['use-case', 'business-logic', 'orchestration'],
1364
+ summary: 'Business logic orchestration following hexagonal architecture. Use cases coordinate repositories and domain operations.'
1365
+ },
1366
+ 'cli': {
1367
+ title: 'Frigg CLI',
1368
+ path: 'packages/devtools/frigg-cli/',
1369
+ topics: ['cli', 'install', 'deploy', 'start', 'validate'],
1370
+ summary: 'Command-line interface for Frigg development. Commands: install, search, start, deploy, validate. Manages API modules and infrastructure.'
1371
+ },
1372
+ 'infrastructure': {
1373
+ title: 'Infrastructure as Code',
1374
+ path: 'packages/devtools/infrastructure/',
1375
+ topics: ['serverless', 'aws', 'lambda', 'vpc', 'deployment'],
1376
+ summary: 'AWS infrastructure generation and deployment. Creates serverless.yml, discovers VPC/KMS resources, manages IAM policies.'
1377
+ },
1378
+ 'testing': {
1379
+ title: 'Testing Patterns',
1380
+ path: 'packages/core/integrations/test/',
1381
+ topics: ['testing', 'jest', 'mock', 'integration-test'],
1382
+ summary: 'Testing strategies for Frigg integrations. Includes mock API utilities, test doubles, and integration test patterns.'
1383
+ }
1384
+ };
1385
+
1386
+ async function searchDocsHandler({ query, topic, limit = 5 }) {
1387
+ const results = [];
1388
+ const queryLower = query.toLowerCase();
1389
+ const queryWords = queryLower.split(/\s+/);
1390
+
1391
+ for (const [key, doc] of Object.entries(DOCS_INDEX)) {
1392
+ let score = 0;
1393
+
1394
+ if (topic && doc.topics.includes(topic.toLowerCase())) {
1395
+ score += 50;
1396
+ }
1397
+
1398
+ for (const word of queryWords) {
1399
+ if (doc.title.toLowerCase().includes(word)) {
1400
+ score += 30;
1401
+ }
1402
+ if (doc.summary.toLowerCase().includes(word)) {
1403
+ score += 20;
1404
+ }
1405
+ if (doc.topics.some(t => t.includes(word))) {
1406
+ score += 25;
1407
+ }
1408
+ if (key.includes(word)) {
1409
+ score += 15;
1410
+ }
1411
+ }
1412
+
1413
+ if (score > 0) {
1414
+ results.push({
1415
+ key,
1416
+ ...doc,
1417
+ score
1418
+ });
1419
+ }
1420
+ }
1421
+
1422
+ results.sort((a, b) => b.score - a.score);
1423
+
1424
+ return {
1425
+ query,
1426
+ topic,
1427
+ results: results.slice(0, limit).map(r => ({
1428
+ title: r.title,
1429
+ path: r.path,
1430
+ summary: r.summary,
1431
+ topics: r.topics,
1432
+ relevance: Math.min(100, r.score)
1433
+ })),
1434
+ totalMatches: results.length
1435
+ };
1436
+ }
1437
+
1438
+ async function readDocsHandler({ docKey, section }) {
1439
+ const doc = DOCS_INDEX[docKey];
1440
+
1441
+ if (!doc) {
1442
+ return {
1443
+ error: `Unknown documentation key: ${docKey}`,
1444
+ availableDocs: Object.entries(DOCS_INDEX).map(([key, d]) => ({
1445
+ key,
1446
+ title: d.title,
1447
+ topics: d.topics
1448
+ }))
1449
+ };
1450
+ }
1451
+
1452
+ const content = {
1453
+ key: docKey,
1454
+ title: doc.title,
1455
+ path: doc.path,
1456
+ summary: doc.summary,
1457
+ topics: doc.topics
1458
+ };
1459
+
1460
+ if (docKey === 'integration-base') {
1461
+ content.sections = {
1462
+ 'static-definition': {
1463
+ title: 'Static Definition',
1464
+ content: `Integration classes must define a static Definition property:
1465
+
1466
+ \`\`\`javascript
1467
+ static Definition = {
1468
+ name: 'integration-name', // Unique identifier
1469
+ version: '1.0.0', // Semantic version
1470
+ modules: { // API modules used
1471
+ moduleName: { definition: require('@friggframework/api-module-name') }
1472
+ },
1473
+ options: {
1474
+ type: 'api', // api, webhook, sync, transform, custom
1475
+ hasUserConfig: true, // Requires user configuration
1476
+ display: {
1477
+ name: 'Display Name',
1478
+ description: 'Integration description',
1479
+ category: 'CRM', // CRM, Finance, Communication, etc.
1480
+ icon: 'icon-name'
1481
+ }
1482
+ },
1483
+ capabilities: {
1484
+ auth: ['oauth2'], // oauth2, api-key, basic, token, custom
1485
+ webhooks: true,
1486
+ sync: { bidirectional: true, incremental: true }
1487
+ }
1488
+ };
1489
+ \`\`\``
1490
+ },
1491
+ 'lifecycle-methods': {
1492
+ title: 'Lifecycle Methods',
1493
+ content: `Required lifecycle methods:
1494
+
1495
+ \`\`\`javascript
1496
+ // Called when integration is created
1497
+ async onCreate({ integrationId }) {
1498
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
1499
+ }
1500
+
1501
+ // Called when integration config is updated
1502
+ async onUpdate(params) {
1503
+ await this.validateConfig();
1504
+ }
1505
+
1506
+ // Called when integration is deleted
1507
+ async onDelete(params) {
1508
+ // Cleanup: unregister webhooks, clear data
1509
+ }
1510
+
1511
+ // Returns form configuration
1512
+ async getConfigOptions() {
1513
+ return { jsonSchema: {}, uiSchema: {} };
1514
+ }
1515
+
1516
+ // Verify authentication is valid
1517
+ async testAuth() {
1518
+ const module = this.getModule('moduleName');
1519
+ return module.testAuth();
1520
+ }
1521
+ \`\`\``
1522
+ },
1523
+ 'webhook-methods': {
1524
+ title: 'Webhook Methods',
1525
+ content: `Webhook handling methods:
1526
+
1527
+ \`\`\`javascript
1528
+ // HTTP handler - no database context
1529
+ async onWebhookReceived({ req, res }) {
1530
+ // Validate signature first
1531
+ const signature = req.headers['x-webhook-signature'];
1532
+ if (!this.verifySignature(req.body, signature)) {
1533
+ return res.status(401).json({ error: 'Invalid signature' });
1534
+ }
1535
+
1536
+ // Queue for processing
1537
+ await this.queueWebhook({
1538
+ integrationId: req.params.integrationId,
1539
+ body: req.body,
1540
+ headers: req.headers
1541
+ });
1542
+ res.status(200).json({ received: true });
1543
+ }
1544
+
1545
+ // Queue worker - has database context
1546
+ async onWebhook({ data }) {
1547
+ const { body } = data;
1548
+ // Process webhook event with full integration context
1549
+ }
1550
+ \`\`\``
1551
+ }
1552
+ };
1553
+ }
1554
+
1555
+ if (docKey === 'forms-config') {
1556
+ content.sections = {
1557
+ 'json-schema': {
1558
+ title: 'JSON Schema',
1559
+ content: `Use JSON Schema to define configuration options:
1560
+
1561
+ \`\`\`javascript
1562
+ async getConfigOptions() {
1563
+ return {
1564
+ jsonSchema: {
1565
+ type: 'object',
1566
+ required: ['workspace'],
1567
+ properties: {
1568
+ workspace: {
1569
+ type: 'string',
1570
+ title: 'Workspace',
1571
+ enum: ['ws1', 'ws2'], // Static options
1572
+ enumNames: ['Workspace 1', 'Workspace 2']
1573
+ },
1574
+ syncEnabled: {
1575
+ type: 'boolean',
1576
+ title: 'Enable Sync',
1577
+ default: true
1578
+ },
1579
+ syncInterval: {
1580
+ type: 'integer',
1581
+ title: 'Sync Interval (minutes)',
1582
+ minimum: 5,
1583
+ maximum: 1440,
1584
+ default: 60
1585
+ }
1586
+ }
1587
+ },
1588
+ uiSchema: {
1589
+ workspace: { 'ui:placeholder': 'Select workspace...' },
1590
+ syncInterval: { 'ui:widget': 'range' }
1591
+ }
1592
+ };
1593
+ }
1594
+ \`\`\``
1595
+ },
1596
+ 'dynamic-options': {
1597
+ title: 'Dynamic Options',
1598
+ content: `Fetch options dynamically from the API:
1599
+
1600
+ \`\`\`javascript
1601
+ async getConfigOptions() {
1602
+ const module = this.getModule('myModule');
1603
+ const workspaces = await module.listWorkspaces();
1604
+
1605
+ return {
1606
+ jsonSchema: {
1607
+ type: 'object',
1608
+ properties: {
1609
+ workspace: {
1610
+ type: 'string',
1611
+ title: 'Workspace',
1612
+ enum: workspaces.map(w => w.id),
1613
+ enumNames: workspaces.map(w => w.name)
1614
+ }
1615
+ }
1616
+ },
1617
+ uiSchema: {}
1618
+ };
1619
+ }
1620
+
1621
+ // Refresh specific option
1622
+ async refreshConfigOptions({ configKey, currentConfig }) {
1623
+ if (configKey === 'workspace') {
1624
+ const workspaces = await this.getModule('myModule').listWorkspaces();
1625
+ return { options: workspaces.map(w => ({ value: w.id, label: w.name })) };
1626
+ }
1627
+ return null;
1628
+ }
1629
+ \`\`\``
1630
+ }
1631
+ };
1632
+ }
1633
+
1634
+ if (docKey === 'webhooks') {
1635
+ content.sections = {
1636
+ 'signature-verification': {
1637
+ title: 'Signature Verification',
1638
+ content: `Always verify webhook signatures:
1639
+
1640
+ \`\`\`javascript
1641
+ const crypto = require('crypto');
1642
+
1643
+ verifySignature(body, signature, secret) {
1644
+ const expected = crypto
1645
+ .createHmac('sha256', secret)
1646
+ .update(JSON.stringify(body))
1647
+ .digest('hex');
1648
+
1649
+ // Use timing-safe comparison
1650
+ return crypto.timingSafeEqual(
1651
+ Buffer.from(signature || '', 'utf8'),
1652
+ Buffer.from(expected, 'utf8')
1653
+ );
1654
+ }
1655
+ \`\`\``
1656
+ },
1657
+ 'event-processing': {
1658
+ title: 'Event Processing',
1659
+ content: `Process webhook events in onWebhook:
1660
+
1661
+ \`\`\`javascript
1662
+ async onWebhook({ data }) {
1663
+ const { body, headers } = data;
1664
+ const eventType = headers['x-event-type'] || body.event;
1665
+
1666
+ switch (eventType) {
1667
+ case 'contact.created':
1668
+ await this.handleContactCreated(body.data);
1669
+ break;
1670
+ case 'deal.updated':
1671
+ await this.handleDealUpdated(body.data);
1672
+ break;
1673
+ case 'contact.deleted':
1674
+ await this.handleContactDeleted(body.data);
1675
+ break;
1676
+ default:
1677
+ console.log('Unhandled event:', eventType);
1678
+ }
1679
+ }
1680
+ \`\`\``
1681
+ }
1682
+ };
1683
+ }
1684
+
1685
+ if (section && content.sections?.[section]) {
1686
+ return {
1687
+ ...content,
1688
+ section: content.sections[section]
1689
+ };
1690
+ }
1691
+
1692
+ return content;
1693
+ }
1694
+
1695
+ function createFriggMcpTools(options = {}) {
1696
+ if (options.gitCheckpointService) {
1697
+ setGitCheckpointService(options.gitCheckpointService);
1698
+ }
1699
+
1700
+ return [
1701
+ {
1702
+ name: 'frigg_validate_schema',
1703
+ description: 'Validate integration, API module, or app definitions against Frigg schemas. Checks required fields, valid enums, and structural correctness.',
1704
+ inputSchema: {
1705
+ type: 'object',
1706
+ required: ['schemaType', 'content'],
1707
+ properties: {
1708
+ schemaType: {
1709
+ type: 'string',
1710
+ enum: ['app-definition', 'integration-definition', 'api-module-definition'],
1711
+ description: 'Type of schema to validate against'
1712
+ },
1713
+ content: {
1714
+ type: 'string',
1715
+ description: 'JSON content to validate (as string or object)'
1716
+ }
1717
+ }
1718
+ },
1719
+ handler: validateSchemaHandler
1720
+ },
1721
+ {
1722
+ name: 'frigg_get_template',
1723
+ description: 'Get starter templates for Frigg integrations based on category (CRM, Finance, Communication, etc.) rather than auth type.',
1724
+ inputSchema: {
1725
+ type: 'object',
1726
+ required: ['category', 'integrationName'],
1727
+ properties: {
1728
+ category: {
1729
+ type: 'string',
1730
+ enum: ['CRM', 'Finance', 'Communication', 'ECommerce', 'Storage', 'Webhook', 'Sync'],
1731
+ description: 'Integration category determines the template structure'
1732
+ },
1733
+ integrationName: {
1734
+ type: 'string',
1735
+ description: 'Name for the integration (e.g., "hubspot", "stripe")'
1736
+ },
1737
+ options: {
1738
+ type: 'object',
1739
+ properties: {
1740
+ webhooks: { type: 'boolean', description: 'Include webhook handling' },
1741
+ authType: { type: 'string', description: 'Override default auth type' },
1742
+ sourceModule: { type: 'string', description: 'For Sync: source API module' },
1743
+ targetModule: { type: 'string', description: 'For Sync: target API module' }
1744
+ }
1745
+ }
1746
+ }
1747
+ },
1748
+ handler: getTemplateHandler
1749
+ },
1750
+ {
1751
+ name: 'frigg_check_patterns',
1752
+ description: 'Verify code follows Frigg architectural patterns. Checks for required base classes, lifecycle methods, and proper structure.',
1753
+ inputSchema: {
1754
+ type: 'object',
1755
+ required: ['code', 'fileType'],
1756
+ properties: {
1757
+ code: { type: 'string', description: 'Source code to analyze' },
1758
+ fileType: {
1759
+ type: 'string',
1760
+ enum: ['integration', 'api-module', 'handler', 'use-case', 'repository'],
1761
+ description: 'Type of file being checked'
1762
+ }
1763
+ }
1764
+ },
1765
+ handler: checkPatternsHandler
1766
+ },
1767
+ {
1768
+ name: 'frigg_list_modules',
1769
+ description: 'List available Frigg API modules from npm registry. Searches @friggframework/api-module-* packages.',
1770
+ inputSchema: {
1771
+ type: 'object',
1772
+ properties: {
1773
+ category: {
1774
+ type: 'string',
1775
+ enum: ['CRM', 'Marketing', 'Communication', 'Finance', 'ECommerce', 'Analytics', 'Storage', 'Development', 'Productivity', 'Social', 'Other', 'all'],
1776
+ description: 'Filter by category'
1777
+ }
1778
+ }
1779
+ },
1780
+ handler: listModulesHandler
1781
+ },
1782
+ {
1783
+ name: 'frigg_run_tests',
1784
+ description: 'Execute Jest tests for generated code',
1785
+ inputSchema: {
1786
+ type: 'object',
1787
+ properties: {
1788
+ testPattern: {
1789
+ type: 'string',
1790
+ description: 'Test file pattern or path (e.g., "integration.test.js")'
1791
+ },
1792
+ coverage: {
1793
+ type: 'boolean',
1794
+ description: 'Generate coverage report'
1795
+ },
1796
+ watch: {
1797
+ type: 'boolean',
1798
+ description: 'Run in watch mode'
1799
+ }
1800
+ }
1801
+ },
1802
+ handler: runTestsHandler
1803
+ },
1804
+ {
1805
+ name: 'frigg_security_scan',
1806
+ description: 'Scan code for security vulnerabilities including hardcoded credentials, injection risks, and missing validation',
1807
+ inputSchema: {
1808
+ type: 'object',
1809
+ required: ['code'],
1810
+ properties: {
1811
+ code: { type: 'string', description: 'Code to scan' },
1812
+ scanType: {
1813
+ type: 'string',
1814
+ enum: ['full', 'credentials', 'injection', 'validation'],
1815
+ description: 'Type of security scan'
1816
+ }
1817
+ }
1818
+ },
1819
+ handler: securityScanHandler
1820
+ },
1821
+ {
1822
+ name: 'frigg_git_checkpoint',
1823
+ description: 'Create git checkpoint before making changes. Records current HEAD for potential rollback.',
1824
+ inputSchema: {
1825
+ type: 'object',
1826
+ required: ['message'],
1827
+ properties: {
1828
+ message: {
1829
+ type: 'string',
1830
+ description: 'Checkpoint description'
1831
+ }
1832
+ }
1833
+ },
1834
+ handler: gitCheckpointHandler
1835
+ },
1836
+ {
1837
+ name: 'frigg_get_example',
1838
+ description: 'Get working examples of specific Frigg patterns and implementations',
1839
+ inputSchema: {
1840
+ type: 'object',
1841
+ required: ['pattern'],
1842
+ properties: {
1843
+ pattern: {
1844
+ type: 'string',
1845
+ enum: ['crm-integration', 'webhook-handler', 'form-config', 'oauth2-flow', 'sync-pattern', 'api-module-complete'],
1846
+ description: 'Pattern to get example for'
1847
+ }
1848
+ }
1849
+ },
1850
+ handler: getExampleHandler
1851
+ },
1852
+ {
1853
+ name: 'frigg_search_docs',
1854
+ description: 'Search Frigg documentation by keyword or topic. Returns relevant documentation sections with relevance scores.',
1855
+ inputSchema: {
1856
+ type: 'object',
1857
+ required: ['query'],
1858
+ properties: {
1859
+ query: {
1860
+ type: 'string',
1861
+ description: 'Search query (e.g., "webhook signature", "json schema forms")'
1862
+ },
1863
+ topic: {
1864
+ type: 'string',
1865
+ enum: ['integration', 'api-module', 'webhooks', 'forms', 'encryption', 'testing', 'cli', 'deployment'],
1866
+ description: 'Filter by topic'
1867
+ },
1868
+ limit: {
1869
+ type: 'integer',
1870
+ description: 'Maximum number of results (default: 5)',
1871
+ default: 5
1872
+ }
1873
+ }
1874
+ },
1875
+ handler: searchDocsHandler
1876
+ },
1877
+ {
1878
+ name: 'frigg_read_docs',
1879
+ description: 'Read detailed documentation for a specific Frigg topic with code examples.',
1880
+ inputSchema: {
1881
+ type: 'object',
1882
+ required: ['docKey'],
1883
+ properties: {
1884
+ docKey: {
1885
+ type: 'string',
1886
+ enum: ['integration-base', 'api-module', 'forms-config', 'webhooks', 'encryption', 'repositories', 'use-cases', 'cli', 'infrastructure', 'testing'],
1887
+ description: 'Documentation key to read'
1888
+ },
1889
+ section: {
1890
+ type: 'string',
1891
+ description: 'Specific section to read (e.g., "lifecycle-methods", "json-schema")'
1892
+ }
1893
+ }
1894
+ },
1895
+ handler: readDocsHandler
1896
+ }
1897
+ ];
1898
+ }
1899
+
1900
+ module.exports = {
1901
+ createFriggMcpTools,
1902
+ validateSchemaHandler,
1903
+ getTemplateHandler,
1904
+ checkPatternsHandler,
1905
+ listModulesHandler,
1906
+ runTestsHandler,
1907
+ securityScanHandler,
1908
+ gitCheckpointHandler,
1909
+ getExampleHandler,
1910
+ searchDocsHandler,
1911
+ readDocsHandler,
1912
+ setGitCheckpointService,
1913
+ NPMRegistryService,
1914
+ INTEGRATION_CATEGORIES,
1915
+ INTEGRATION_TYPES,
1916
+ CATEGORY_TEMPLATES,
1917
+ DOCS_INDEX
1918
+ };