@l4yercak3/cli 1.1.11 → 1.2.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.
@@ -0,0 +1,696 @@
1
+ /**
2
+ * Forms Domain Tools
3
+ *
4
+ * Form builder tools for creating forms, managing fields,
5
+ * and retrieving form responses.
6
+ *
7
+ * @module mcp/registry/domains/forms
8
+ */
9
+
10
+ const backendClient = require('../../../api/backend-client');
11
+
12
+ /**
13
+ * Forms domain definition
14
+ */
15
+ module.exports = {
16
+ name: 'forms',
17
+ description: 'Form builder - registration forms, surveys, applications',
18
+ tools: [
19
+ // ========================================
20
+ // Form CRUD Tools
21
+ // ========================================
22
+ {
23
+ name: 'l4yercak3_forms_list',
24
+ description: `List all forms for the organization.
25
+ Returns forms with their type, status, and submission counts.`,
26
+ inputSchema: {
27
+ type: 'object',
28
+ properties: {
29
+ subtype: {
30
+ type: 'string',
31
+ enum: ['registration', 'survey', 'application'],
32
+ description: 'Filter by form type',
33
+ },
34
+ status: {
35
+ type: 'string',
36
+ enum: ['draft', 'published', 'archived'],
37
+ description: 'Filter by form status',
38
+ },
39
+ eventId: {
40
+ type: 'string',
41
+ description: 'Filter forms linked to a specific event',
42
+ },
43
+ },
44
+ },
45
+ requiresAuth: true,
46
+ requiredPermissions: ['view_forms'],
47
+ handler: async (params, authContext) => {
48
+ const queryParams = new URLSearchParams();
49
+ queryParams.set('organizationId', authContext.organizationId);
50
+ if (params.subtype) queryParams.set('subtype', params.subtype);
51
+ if (params.status) queryParams.set('status', params.status);
52
+ if (params.eventId) queryParams.set('eventId', params.eventId);
53
+
54
+ const response = await backendClient.request(
55
+ 'GET',
56
+ `/api/v1/forms?${queryParams.toString()}`
57
+ );
58
+
59
+ return {
60
+ forms: (response.forms || []).map(form => ({
61
+ id: form._id,
62
+ name: form.name,
63
+ description: form.description,
64
+ subtype: form.subtype,
65
+ status: form.status,
66
+ eventId: form.customProperties?.eventId,
67
+ fieldCount: form.customProperties?.formSchema?.fields?.length || 0,
68
+ submissionCount: form.customProperties?.stats?.submissions || 0,
69
+ publicUrl: form.customProperties?.publicUrl,
70
+ createdAt: form.createdAt,
71
+ })),
72
+ total: response.total || (response.forms || []).length,
73
+ };
74
+ },
75
+ },
76
+
77
+ {
78
+ name: 'l4yercak3_forms_create',
79
+ description: `Create a new form.
80
+ Forms can be registration forms, surveys, or applications.
81
+ Start with basic info, then add fields.`,
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ name: {
86
+ type: 'string',
87
+ description: 'Form name',
88
+ },
89
+ description: {
90
+ type: 'string',
91
+ description: 'Form description',
92
+ },
93
+ subtype: {
94
+ type: 'string',
95
+ enum: ['registration', 'survey', 'application'],
96
+ description: 'Form type (default: registration)',
97
+ },
98
+ eventId: {
99
+ type: 'string',
100
+ description: 'Link form to an event (optional)',
101
+ },
102
+ fields: {
103
+ type: 'array',
104
+ items: {
105
+ type: 'object',
106
+ properties: {
107
+ id: {
108
+ type: 'string',
109
+ description: 'Unique field ID (auto-generated if not provided)',
110
+ },
111
+ type: {
112
+ type: 'string',
113
+ enum: [
114
+ 'text',
115
+ 'textarea',
116
+ 'email',
117
+ 'phone',
118
+ 'number',
119
+ 'date',
120
+ 'time',
121
+ 'datetime',
122
+ 'select',
123
+ 'radio',
124
+ 'checkbox',
125
+ 'multi_select',
126
+ 'file',
127
+ 'rating',
128
+ 'section_header',
129
+ ],
130
+ description: 'Field type',
131
+ },
132
+ label: {
133
+ type: 'string',
134
+ description: 'Field label',
135
+ },
136
+ placeholder: {
137
+ type: 'string',
138
+ description: 'Placeholder text',
139
+ },
140
+ required: {
141
+ type: 'boolean',
142
+ description: 'Is field required?',
143
+ },
144
+ options: {
145
+ type: 'array',
146
+ items: {
147
+ type: 'object',
148
+ properties: {
149
+ value: { type: 'string' },
150
+ label: { type: 'string' },
151
+ },
152
+ },
153
+ description: 'Options for select/radio/checkbox fields',
154
+ },
155
+ validation: {
156
+ type: 'object',
157
+ properties: {
158
+ min: { type: 'number' },
159
+ max: { type: 'number' },
160
+ pattern: { type: 'string' },
161
+ message: { type: 'string' },
162
+ },
163
+ description: 'Validation rules',
164
+ },
165
+ },
166
+ required: ['type', 'label'],
167
+ },
168
+ description: 'Form fields',
169
+ },
170
+ settings: {
171
+ type: 'object',
172
+ properties: {
173
+ allowMultipleSubmissions: {
174
+ type: 'boolean',
175
+ description: 'Allow same person to submit multiple times',
176
+ },
177
+ showProgressBar: {
178
+ type: 'boolean',
179
+ description: 'Show progress bar for multi-step forms',
180
+ },
181
+ submitButtonText: {
182
+ type: 'string',
183
+ description: 'Custom submit button text',
184
+ },
185
+ successMessage: {
186
+ type: 'string',
187
+ description: 'Message shown after submission',
188
+ },
189
+ redirectUrl: {
190
+ type: 'string',
191
+ description: 'URL to redirect to after submission',
192
+ },
193
+ },
194
+ description: 'Form settings',
195
+ },
196
+ },
197
+ required: ['name'],
198
+ },
199
+ requiresAuth: true,
200
+ requiredPermissions: ['manage_forms'],
201
+ handler: async (params, authContext) => {
202
+ // Build form schema
203
+ const formSchema = {
204
+ version: '1.0',
205
+ fields: (params.fields || []).map((field, index) => ({
206
+ id: field.id || `field_${index + 1}`,
207
+ type: field.type,
208
+ label: field.label,
209
+ placeholder: field.placeholder,
210
+ required: field.required || false,
211
+ options: field.options,
212
+ validation: field.validation,
213
+ order: index,
214
+ })),
215
+ settings: {
216
+ allowMultipleSubmissions: params.settings?.allowMultipleSubmissions || false,
217
+ showProgressBar: params.settings?.showProgressBar || true,
218
+ submitButtonText: params.settings?.submitButtonText || 'Submit',
219
+ successMessage: params.settings?.successMessage || 'Thank you for your submission!',
220
+ redirectUrl: params.settings?.redirectUrl || null,
221
+ displayMode: 'all',
222
+ },
223
+ sections: [],
224
+ };
225
+
226
+ const response = await backendClient.request('POST', '/api/v1/forms', {
227
+ organizationId: authContext.organizationId,
228
+ name: params.name,
229
+ description: params.description,
230
+ subtype: params.subtype || 'registration',
231
+ eventId: params.eventId,
232
+ formSchema,
233
+ });
234
+
235
+ return {
236
+ success: true,
237
+ formId: response.formId || response.id,
238
+ status: 'draft',
239
+ message: `Created form: ${params.name}`,
240
+ nextSteps: [
241
+ 'Add more fields with l4yercak3_forms_add_field if needed',
242
+ 'Publish the form with l4yercak3_forms_publish',
243
+ ],
244
+ };
245
+ },
246
+ },
247
+
248
+ {
249
+ name: 'l4yercak3_forms_get',
250
+ description: `Get detailed information about a form including all fields.`,
251
+ inputSchema: {
252
+ type: 'object',
253
+ properties: {
254
+ formId: {
255
+ type: 'string',
256
+ description: 'The form ID',
257
+ },
258
+ },
259
+ required: ['formId'],
260
+ },
261
+ requiresAuth: true,
262
+ requiredPermissions: ['view_forms'],
263
+ handler: async (params, authContext) => {
264
+ const response = await backendClient.request('GET', `/api/v1/forms/${params.formId}`);
265
+
266
+ const form = response.form || response;
267
+ const schema = form.customProperties?.formSchema || {};
268
+
269
+ return {
270
+ id: form._id,
271
+ name: form.name,
272
+ description: form.description,
273
+ subtype: form.subtype,
274
+ status: form.status,
275
+ eventId: form.customProperties?.eventId,
276
+ publicUrl: form.customProperties?.publicUrl,
277
+ fields: schema.fields || [],
278
+ settings: schema.settings || {},
279
+ stats: form.customProperties?.stats || {
280
+ views: 0,
281
+ submissions: 0,
282
+ completionRate: 0,
283
+ },
284
+ createdAt: form.createdAt,
285
+ updatedAt: form.updatedAt,
286
+ };
287
+ },
288
+ },
289
+
290
+ {
291
+ name: 'l4yercak3_forms_update',
292
+ description: `Update form name, description, or settings.
293
+ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields.`,
294
+ inputSchema: {
295
+ type: 'object',
296
+ properties: {
297
+ formId: {
298
+ type: 'string',
299
+ description: 'The form ID to update',
300
+ },
301
+ name: { type: 'string', description: 'New form name' },
302
+ description: { type: 'string', description: 'New description' },
303
+ subtype: {
304
+ type: 'string',
305
+ enum: ['registration', 'survey', 'application'],
306
+ },
307
+ settings: {
308
+ type: 'object',
309
+ description: 'Form settings to update',
310
+ },
311
+ },
312
+ required: ['formId'],
313
+ },
314
+ requiresAuth: true,
315
+ requiredPermissions: ['manage_forms'],
316
+ handler: async (params, authContext) => {
317
+ const { formId, ...updates } = params;
318
+
319
+ await backendClient.request('PATCH', `/api/v1/forms/${formId}`, updates);
320
+
321
+ return {
322
+ success: true,
323
+ formId,
324
+ message: 'Form updated successfully',
325
+ };
326
+ },
327
+ },
328
+
329
+ {
330
+ name: 'l4yercak3_forms_add_field',
331
+ description: `Add a new field to a form.`,
332
+ inputSchema: {
333
+ type: 'object',
334
+ properties: {
335
+ formId: {
336
+ type: 'string',
337
+ description: 'The form ID',
338
+ },
339
+ field: {
340
+ type: 'object',
341
+ properties: {
342
+ type: {
343
+ type: 'string',
344
+ enum: [
345
+ 'text',
346
+ 'textarea',
347
+ 'email',
348
+ 'phone',
349
+ 'number',
350
+ 'date',
351
+ 'time',
352
+ 'datetime',
353
+ 'select',
354
+ 'radio',
355
+ 'checkbox',
356
+ 'multi_select',
357
+ 'file',
358
+ 'rating',
359
+ 'section_header',
360
+ ],
361
+ description: 'Field type',
362
+ },
363
+ label: {
364
+ type: 'string',
365
+ description: 'Field label',
366
+ },
367
+ placeholder: {
368
+ type: 'string',
369
+ description: 'Placeholder text',
370
+ },
371
+ required: {
372
+ type: 'boolean',
373
+ description: 'Is field required?',
374
+ },
375
+ options: {
376
+ type: 'array',
377
+ items: {
378
+ type: 'object',
379
+ properties: {
380
+ value: { type: 'string' },
381
+ label: { type: 'string' },
382
+ },
383
+ },
384
+ description: 'Options for select/radio/checkbox fields',
385
+ },
386
+ },
387
+ required: ['type', 'label'],
388
+ description: 'Field to add',
389
+ },
390
+ insertAfter: {
391
+ type: 'string',
392
+ description: 'Field ID to insert after (optional, adds to end if not specified)',
393
+ },
394
+ },
395
+ required: ['formId', 'field'],
396
+ },
397
+ requiresAuth: true,
398
+ requiredPermissions: ['manage_forms'],
399
+ handler: async (params, authContext) => {
400
+ // Get current form to add field
401
+ const formResponse = await backendClient.request('GET', `/api/v1/forms/${params.formId}`);
402
+ const form = formResponse.form || formResponse;
403
+ const schema = form.customProperties?.formSchema || { fields: [] };
404
+
405
+ // Generate field ID
406
+ const newField = {
407
+ id: `field_${Date.now()}`,
408
+ ...params.field,
409
+ order: schema.fields.length,
410
+ };
411
+
412
+ // Add field
413
+ schema.fields.push(newField);
414
+
415
+ // Update form
416
+ await backendClient.request('PATCH', `/api/v1/forms/${params.formId}`, {
417
+ formSchema: schema,
418
+ });
419
+
420
+ return {
421
+ success: true,
422
+ fieldId: newField.id,
423
+ message: `Added field: ${params.field.label}`,
424
+ };
425
+ },
426
+ },
427
+
428
+ {
429
+ name: 'l4yercak3_forms_publish',
430
+ description: `Publish a form to make it available for submissions.`,
431
+ inputSchema: {
432
+ type: 'object',
433
+ properties: {
434
+ formId: {
435
+ type: 'string',
436
+ description: 'The form ID to publish',
437
+ },
438
+ },
439
+ required: ['formId'],
440
+ },
441
+ requiresAuth: true,
442
+ requiredPermissions: ['manage_forms'],
443
+ handler: async (params, authContext) => {
444
+ await backendClient.request('POST', `/api/v1/forms/${params.formId}/publish`);
445
+
446
+ return {
447
+ success: true,
448
+ formId: params.formId,
449
+ status: 'published',
450
+ message: 'Form published successfully',
451
+ };
452
+ },
453
+ },
454
+
455
+ {
456
+ name: 'l4yercak3_forms_unpublish',
457
+ description: `Unpublish a form (change back to draft status).`,
458
+ inputSchema: {
459
+ type: 'object',
460
+ properties: {
461
+ formId: {
462
+ type: 'string',
463
+ description: 'The form ID to unpublish',
464
+ },
465
+ },
466
+ required: ['formId'],
467
+ },
468
+ requiresAuth: true,
469
+ requiredPermissions: ['manage_forms'],
470
+ handler: async (params, authContext) => {
471
+ await backendClient.request('POST', `/api/v1/forms/${params.formId}/unpublish`);
472
+
473
+ return {
474
+ success: true,
475
+ formId: params.formId,
476
+ status: 'draft',
477
+ message: 'Form unpublished',
478
+ };
479
+ },
480
+ },
481
+
482
+ {
483
+ name: 'l4yercak3_forms_delete',
484
+ description: `Delete a form permanently.`,
485
+ inputSchema: {
486
+ type: 'object',
487
+ properties: {
488
+ formId: {
489
+ type: 'string',
490
+ description: 'The form ID to delete',
491
+ },
492
+ },
493
+ required: ['formId'],
494
+ },
495
+ requiresAuth: true,
496
+ requiredPermissions: ['manage_forms'],
497
+ handler: async (params, authContext) => {
498
+ await backendClient.request('DELETE', `/api/v1/forms/${params.formId}`);
499
+
500
+ return {
501
+ success: true,
502
+ message: 'Form deleted successfully',
503
+ };
504
+ },
505
+ },
506
+
507
+ {
508
+ name: 'l4yercak3_forms_duplicate',
509
+ description: `Create a copy of an existing form.`,
510
+ inputSchema: {
511
+ type: 'object',
512
+ properties: {
513
+ formId: {
514
+ type: 'string',
515
+ description: 'The form ID to duplicate',
516
+ },
517
+ },
518
+ required: ['formId'],
519
+ },
520
+ requiresAuth: true,
521
+ requiredPermissions: ['manage_forms'],
522
+ handler: async (params, authContext) => {
523
+ const response = await backendClient.request(
524
+ 'POST',
525
+ `/api/v1/forms/${params.formId}/duplicate`
526
+ );
527
+
528
+ return {
529
+ success: true,
530
+ newFormId: response.formId || response.id,
531
+ message: 'Form duplicated successfully',
532
+ };
533
+ },
534
+ },
535
+
536
+ // ========================================
537
+ // Form Response Tools
538
+ // ========================================
539
+ {
540
+ name: 'l4yercak3_forms_get_responses',
541
+ description: `Get all responses submitted to a form.`,
542
+ inputSchema: {
543
+ type: 'object',
544
+ properties: {
545
+ formId: {
546
+ type: 'string',
547
+ description: 'The form ID',
548
+ },
549
+ status: {
550
+ type: 'string',
551
+ enum: ['partial', 'complete', 'abandoned'],
552
+ description: 'Filter by response status',
553
+ },
554
+ limit: {
555
+ type: 'number',
556
+ description: 'Max responses to return (default 50)',
557
+ },
558
+ offset: {
559
+ type: 'number',
560
+ description: 'Offset for pagination',
561
+ },
562
+ },
563
+ required: ['formId'],
564
+ },
565
+ requiresAuth: true,
566
+ requiredPermissions: ['view_forms'],
567
+ handler: async (params, authContext) => {
568
+ const queryParams = new URLSearchParams();
569
+ if (params.status) queryParams.set('status', params.status);
570
+ if (params.limit) queryParams.set('limit', Math.min(params.limit, 100));
571
+ if (params.offset) queryParams.set('offset', params.offset);
572
+
573
+ const response = await backendClient.request(
574
+ 'GET',
575
+ `/api/v1/forms/${params.formId}/responses?${queryParams.toString()}`
576
+ );
577
+
578
+ return {
579
+ responses: (response.responses || []).map(resp => ({
580
+ id: resp._id,
581
+ status: resp.status,
582
+ submittedAt: resp.customProperties?.submittedAt,
583
+ data: resp.customProperties?.responses || {},
584
+ isPublicSubmission: resp.customProperties?.isPublicSubmission || false,
585
+ })),
586
+ total: response.total || (response.responses || []).length,
587
+ };
588
+ },
589
+ },
590
+
591
+ {
592
+ name: 'l4yercak3_forms_get_response',
593
+ description: `Get a single form response with full details.`,
594
+ inputSchema: {
595
+ type: 'object',
596
+ properties: {
597
+ responseId: {
598
+ type: 'string',
599
+ description: 'The response ID',
600
+ },
601
+ },
602
+ required: ['responseId'],
603
+ },
604
+ requiresAuth: true,
605
+ requiredPermissions: ['view_forms'],
606
+ handler: async (params, authContext) => {
607
+ const response = await backendClient.request(
608
+ 'GET',
609
+ `/api/v1/forms/responses/${params.responseId}`
610
+ );
611
+
612
+ const resp = response.response || response;
613
+
614
+ return {
615
+ id: resp._id,
616
+ formId: resp.customProperties?.formId,
617
+ status: resp.status,
618
+ submittedAt: resp.customProperties?.submittedAt,
619
+ data: resp.customProperties?.responses || {},
620
+ metadata: {
621
+ userAgent: resp.customProperties?.userAgent,
622
+ ipAddress: resp.customProperties?.ipAddress,
623
+ isPublicSubmission: resp.customProperties?.isPublicSubmission,
624
+ },
625
+ createdAt: resp.createdAt,
626
+ };
627
+ },
628
+ },
629
+
630
+ {
631
+ name: 'l4yercak3_forms_export_responses',
632
+ description: `Export form responses as CSV or JSON.`,
633
+ inputSchema: {
634
+ type: 'object',
635
+ properties: {
636
+ formId: {
637
+ type: 'string',
638
+ description: 'The form ID',
639
+ },
640
+ format: {
641
+ type: 'string',
642
+ enum: ['json', 'csv'],
643
+ description: 'Export format (default: json)',
644
+ },
645
+ },
646
+ required: ['formId'],
647
+ },
648
+ requiresAuth: true,
649
+ requiredPermissions: ['view_forms'],
650
+ handler: async (params, authContext) => {
651
+ const format = params.format || 'json';
652
+
653
+ const response = await backendClient.request(
654
+ 'GET',
655
+ `/api/v1/forms/${params.formId}/responses?limit=1000`
656
+ );
657
+
658
+ const responses = (response.responses || []).map(resp => ({
659
+ id: resp._id,
660
+ submittedAt: resp.customProperties?.submittedAt,
661
+ ...resp.customProperties?.responses,
662
+ }));
663
+
664
+ if (format === 'csv') {
665
+ // Generate CSV
666
+ if (responses.length === 0) {
667
+ return { csv: 'No responses to export', rowCount: 0 };
668
+ }
669
+
670
+ const headers = Object.keys(responses[0]);
671
+ const rows = [
672
+ headers.join(','),
673
+ ...responses.map(r =>
674
+ headers.map(h => {
675
+ const val = r[h];
676
+ if (val === null || val === undefined) return '';
677
+ if (typeof val === 'string' && val.includes(',')) return `"${val}"`;
678
+ return String(val);
679
+ }).join(',')
680
+ ),
681
+ ];
682
+
683
+ return {
684
+ csv: rows.join('\n'),
685
+ rowCount: responses.length,
686
+ };
687
+ }
688
+
689
+ return {
690
+ responses,
691
+ rowCount: responses.length,
692
+ };
693
+ },
694
+ },
695
+ ],
696
+ };