@salesforce/b2c-dx-mcp 0.3.2 → 0.4.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 (54) hide show
  1. package/README.md +47 -27
  2. package/content/page-designer.md +4 -4
  3. package/dist/commands/mcp.d.ts +12 -0
  4. package/dist/commands/mcp.js +16 -4
  5. package/dist/registry.d.ts +4 -4
  6. package/dist/registry.js +10 -10
  7. package/dist/services.d.ts +75 -1
  8. package/dist/services.js +124 -1
  9. package/dist/tools/adapter.d.ts +29 -21
  10. package/dist/tools/adapter.js +34 -24
  11. package/dist/tools/cartridges/index.d.ts +5 -3
  12. package/dist/tools/cartridges/index.js +55 -25
  13. package/dist/tools/index.d.ts +1 -0
  14. package/dist/tools/index.js +1 -0
  15. package/dist/tools/mrt/index.d.ts +2 -2
  16. package/dist/tools/mrt/index.js +29 -10
  17. package/dist/tools/page-designer-decorator/analyzer.d.ts +169 -0
  18. package/dist/tools/page-designer-decorator/analyzer.js +535 -0
  19. package/dist/tools/page-designer-decorator/index.d.ts +252 -0
  20. package/dist/tools/page-designer-decorator/index.js +597 -0
  21. package/dist/tools/page-designer-decorator/rules/1-mode-selection.d.ts +8 -0
  22. package/dist/tools/page-designer-decorator/rules/1-mode-selection.js +65 -0
  23. package/dist/tools/page-designer-decorator/rules/2a-auto-mode.d.ts +13 -0
  24. package/dist/tools/page-designer-decorator/rules/2a-auto-mode.js +87 -0
  25. package/dist/tools/page-designer-decorator/rules/2b-0-interactive-overview.d.ts +4 -0
  26. package/dist/tools/page-designer-decorator/rules/2b-0-interactive-overview.js +55 -0
  27. package/dist/tools/page-designer-decorator/rules/2b-1-interactive-analyze.d.ts +22 -0
  28. package/dist/tools/page-designer-decorator/rules/2b-1-interactive-analyze.js +109 -0
  29. package/dist/tools/page-designer-decorator/rules/2b-2-interactive-select-props.d.ts +21 -0
  30. package/dist/tools/page-designer-decorator/rules/2b-2-interactive-select-props.js +60 -0
  31. package/dist/tools/page-designer-decorator/rules/2b-3-interactive-configure-attrs.d.ts +27 -0
  32. package/dist/tools/page-designer-decorator/rules/2b-3-interactive-configure-attrs.js +68 -0
  33. package/dist/tools/page-designer-decorator/rules/2b-4-interactive-configure-regions.d.ts +4 -0
  34. package/dist/tools/page-designer-decorator/rules/2b-4-interactive-configure-regions.js +65 -0
  35. package/dist/tools/page-designer-decorator/rules/2b-5-interactive-confirm-generation.d.ts +11 -0
  36. package/dist/tools/page-designer-decorator/rules/2b-5-interactive-confirm-generation.js +92 -0
  37. package/dist/tools/page-designer-decorator/rules.d.ts +51 -0
  38. package/dist/tools/page-designer-decorator/rules.js +70 -0
  39. package/dist/tools/page-designer-decorator/templates/decorator-generator.d.ts +116 -0
  40. package/dist/tools/page-designer-decorator/templates/decorator-generator.js +350 -0
  41. package/dist/tools/pwav3/index.d.ts +2 -2
  42. package/dist/tools/pwav3/index.js +13 -13
  43. package/dist/tools/scapi/index.d.ts +10 -2
  44. package/dist/tools/scapi/index.js +5 -56
  45. package/dist/tools/scapi/scapi-custom-apis-status.d.ts +9 -0
  46. package/dist/tools/scapi/scapi-custom-apis-status.js +152 -0
  47. package/dist/tools/scapi/scapi-schemas-list.d.ts +12 -0
  48. package/dist/tools/scapi/scapi-schemas-list.js +248 -0
  49. package/dist/tools/storefrontnext/developer-guidelines.d.ts +2 -2
  50. package/dist/tools/storefrontnext/developer-guidelines.js +3 -3
  51. package/dist/tools/storefrontnext/index.d.ts +2 -2
  52. package/dist/tools/storefrontnext/index.js +13 -13
  53. package/oclif.manifest.json +13 -2
  54. package/package.json +10 -5
@@ -0,0 +1,597 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ import { z } from 'zod';
7
+ import { componentAnalyzer, generateTypeSuggestions, resolveComponent } from './analyzer.js';
8
+ import { generateDecoratorCode } from './templates/decorator-generator.js';
9
+ import { pageDesignerDecoratorRules } from './rules.js';
10
+ // ============================================================================
11
+ // SCHEMA DEFINITION
12
+ // ============================================================================
13
+ export const pageDesignerDecoratorSchema = z
14
+ .object({
15
+ component: z
16
+ .string()
17
+ .describe('Component name (e.g., "ProductCard", "Hero") or file path (e.g., "src/components/ProductCard.tsx"). ' +
18
+ 'When a name is provided, the tool automatically searches common component directories. ' +
19
+ 'For backward compatibility, file paths are also supported.'),
20
+ searchPaths: z
21
+ .array(z.string())
22
+ .optional()
23
+ .describe('Additional directories to search for components (e.g., ["packages/retail/src", "app/features"]). ' +
24
+ 'Only used when component is specified by name (not path).'),
25
+ autoMode: z
26
+ .boolean()
27
+ .optional()
28
+ .describe('Auto-generate all configurations with sensible defaults (skip interactive workflow). When enabled, automatically selects suitable props, infers types, and generates decorators without user confirmation.'),
29
+ componentId: z.string().optional().describe('Override component ID (default: auto-generated from component name)'),
30
+ conversationContext: z
31
+ .object({
32
+ step: z
33
+ .enum(['analyze', 'select_props', 'configure_attrs', 'configure_regions', 'confirm_generation'])
34
+ .optional()
35
+ .describe('Current step in the conversation workflow'),
36
+ componentInfo: z
37
+ .record(z.string(), z.any())
38
+ .optional()
39
+ .describe('Cached component analysis from previous step'),
40
+ selectedProps: z
41
+ .array(z.string())
42
+ .optional()
43
+ .describe('Props from component interface selected to expose in Page Designer'),
44
+ newAttributes: z
45
+ .array(z.object({
46
+ name: z.string(),
47
+ description: z.string().optional(),
48
+ required: z.boolean().optional(),
49
+ }))
50
+ .optional()
51
+ .describe('New attributes to add (not in existing props)'),
52
+ attributeConfig: z
53
+ .record(z.string(), z.object({
54
+ type: z.string().optional(),
55
+ name: z.string().optional(),
56
+ defaultValue: z.any().optional(),
57
+ values: z.array(z.string()).optional(),
58
+ }))
59
+ .optional()
60
+ .describe('Configuration for each attribute (explicit types, names, etc.)'),
61
+ componentMetadata: z
62
+ .object({
63
+ id: z.string(),
64
+ name: z.string(),
65
+ description: z.string(),
66
+ group: z.string().optional(),
67
+ })
68
+ .optional()
69
+ .describe('Component decorator configuration'),
70
+ regionConfig: z
71
+ .object({
72
+ enabled: z.boolean().describe('Whether to include @RegionDefinition decorator'),
73
+ regions: z
74
+ .array(z.object({
75
+ id: z.string().describe('Region identifier (e.g., "main", "sidebar")'),
76
+ name: z.string().describe('Display name for the region'),
77
+ description: z.string().optional().describe('Description of the region purpose'),
78
+ maxComponents: z.number().optional().describe('Maximum number of components allowed in region'),
79
+ componentTypeInclusions: z
80
+ .array(z.string())
81
+ .optional()
82
+ .describe('Allowed component types (whitelist)'),
83
+ componentTypeExclusions: z
84
+ .array(z.string())
85
+ .optional()
86
+ .describe('Disallowed component types (blacklist)'),
87
+ }))
88
+ .optional()
89
+ .describe('Array of region definitions'),
90
+ })
91
+ .optional()
92
+ .describe('Region configuration for nested content areas'),
93
+ })
94
+ .optional()
95
+ .describe('Conversation state for multi-turn interaction'),
96
+ })
97
+ .strict();
98
+ // ============================================================================
99
+ // HELPER FUNCTIONS
100
+ // ============================================================================
101
+ /**
102
+ * Convert component name to kebab-case for use as component ID
103
+ *
104
+ * Page Designer component IDs should be lowercase with hyphens.
105
+ *
106
+ * @param name - PascalCase or camelCase name
107
+ * @returns kebab-case identifier
108
+ *
109
+ * @example
110
+ * toKebabCase('ProductCard') // => 'product-card'
111
+ * toKebabCase('TwoColumnLayout') // => 'two-column-layout'
112
+ *
113
+ * @internal
114
+ */
115
+ function toKebabCase(name) {
116
+ return name
117
+ .replaceAll(/([a-z])([A-Z])/g, '$1-$2')
118
+ .replaceAll(/[\s_]+/g, '-')
119
+ .toLowerCase();
120
+ }
121
+ /**
122
+ * Convert camelCase prop name to human-readable display name
123
+ *
124
+ * Used for attribute names shown to merchants in Page Designer UI.
125
+ *
126
+ * @param fieldName - camelCase field name
127
+ * @returns Human-readable name with proper capitalization
128
+ *
129
+ * @example
130
+ * toHumanReadableName('imageUrl') // => 'Image Url'
131
+ * toHumanReadableName('ctaButtonText') // => 'Cta Button Text'
132
+ *
133
+ * @internal
134
+ */
135
+ function toHumanReadableName(fieldName) {
136
+ return fieldName
137
+ .replaceAll(/([A-Z])/g, ' $1')
138
+ .replace(/^./, (str) => str.toUpperCase())
139
+ .trim();
140
+ }
141
+ // ============================================================================
142
+ // WORKFLOW STEP HANDLERS
143
+ // ============================================================================
144
+ /**
145
+ * Handle Interactive Mode - Step 1: Analyze
146
+ *
147
+ * Parses the component file and provides analysis to the LLM:
148
+ * - Component name and structure
149
+ * - All props with types
150
+ * - Categorization (editable, complex, UI-only)
151
+ * - Suggested component ID and name
152
+ *
153
+ * **LLM should then:**
154
+ * - Present findings to user
155
+ * - Ask which props to expose in Page Designer
156
+ * - Collect component metadata (ID, name, description, group)
157
+ * - Call next step with selectedProps and componentMetadata
158
+ *
159
+ * @internal
160
+ */
161
+ function handleAnalyzeStep(args, workspaceRoot) {
162
+ const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
163
+ const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
164
+ const editableProps = componentInfo.props.filter((p) => !p.isComplex && !p.isUIOnly);
165
+ const complexProps = componentInfo.props.filter((p) => p.isComplex);
166
+ const uiProps = componentInfo.props.filter((p) => p.isUIOnly && !p.isComplex);
167
+ const suggestedComponentId = args.componentId || toKebabCase(componentInfo.componentName);
168
+ const suggestedComponentName = toHumanReadableName(componentInfo.componentName);
169
+ const instructions = pageDesignerDecoratorRules.getAnalyzeInstructions({
170
+ componentName: componentInfo.componentName,
171
+ file: args.component,
172
+ hasDecorators: componentInfo.hasDecorators,
173
+ interfaceName: componentInfo.interfaceName || 'None found',
174
+ totalProps: componentInfo.props.length,
175
+ exportType: componentInfo.exportType,
176
+ hasEditableProps: editableProps.length > 0,
177
+ editableProps,
178
+ hasComplexProps: complexProps.length > 0,
179
+ complexProps,
180
+ hasUIProps: uiProps.length > 0,
181
+ uiProps,
182
+ suggestedComponentId,
183
+ suggestedComponentName,
184
+ });
185
+ return {
186
+ content: [
187
+ {
188
+ type: 'text',
189
+ text: instructions,
190
+ },
191
+ ],
192
+ };
193
+ }
194
+ function handleSelectPropsStep(args, _workspaceRoot) {
195
+ const selectedProps = args.conversationContext?.selectedProps || [];
196
+ const newAttributes = args.conversationContext?.newAttributes || [];
197
+ const componentMetadata = args.conversationContext?.componentMetadata;
198
+ if (!componentMetadata) {
199
+ return {
200
+ content: [
201
+ {
202
+ type: 'text',
203
+ text: '⚠️ Missing component metadata. Please provide component ID, name, description, and group from the analyze step.',
204
+ },
205
+ ],
206
+ isError: true,
207
+ };
208
+ }
209
+ const confirmation = pageDesignerDecoratorRules.getSelectPropsConfirmation({
210
+ componentMetadata: {
211
+ id: componentMetadata.id,
212
+ name: componentMetadata.name,
213
+ description: componentMetadata.description,
214
+ group: componentMetadata.group || 'odyssey_base',
215
+ },
216
+ selectedProps,
217
+ newAttributes,
218
+ selectedPropsCount: selectedProps.length,
219
+ newAttributesCount: newAttributes.length,
220
+ totalAttributeCount: selectedProps.length + newAttributes.length,
221
+ hasSelectedProps: selectedProps.length > 0,
222
+ hasNewAttributes: newAttributes.length > 0,
223
+ });
224
+ return {
225
+ content: [
226
+ {
227
+ type: 'text',
228
+ text: confirmation,
229
+ },
230
+ ],
231
+ };
232
+ }
233
+ function handleConfigureAttrsStep(args, workspaceRoot) {
234
+ const selectedProps = args.conversationContext?.selectedProps || [];
235
+ const newAttributes = args.conversationContext?.newAttributes || [];
236
+ const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
237
+ const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
238
+ const attributeAnalysis = [];
239
+ for (const propName of selectedProps) {
240
+ const prop = componentInfo.props.find((p) => p.name === propName);
241
+ if (!prop)
242
+ continue;
243
+ const suggestions = generateTypeSuggestions(propName, prop.type);
244
+ attributeAnalysis.push({
245
+ name: propName,
246
+ source: 'existing',
247
+ tsType: prop.type,
248
+ autoInferred: suggestions.length === 0,
249
+ suggestions,
250
+ });
251
+ }
252
+ for (const attr of newAttributes) {
253
+ const suggestions = generateTypeSuggestions(attr.name, 'string');
254
+ attributeAnalysis.push({
255
+ name: attr.name,
256
+ source: 'new',
257
+ tsType: 'string',
258
+ autoInferred: suggestions.length === 0,
259
+ suggestions,
260
+ });
261
+ }
262
+ const autoInferredAttrs = attributeAnalysis.filter((a) => a.autoInferred);
263
+ const needsConfigAttrs = attributeAnalysis.filter((a) => !a.autoInferred);
264
+ const instructions = pageDesignerDecoratorRules.getConfigureAttrsInstructions({
265
+ totalAttributes: attributeAnalysis.length,
266
+ autoInferredCount: autoInferredAttrs.length,
267
+ needsConfigCount: needsConfigAttrs.length,
268
+ hasAutoInferred: autoInferredAttrs.length > 0,
269
+ autoInferredAttrs: autoInferredAttrs.map((a) => ({ name: a.name, tsType: a.tsType })),
270
+ hasNeedsConfig: needsConfigAttrs.length > 0,
271
+ needsConfigAttrs: needsConfigAttrs.map((attr) => ({
272
+ name: attr.name,
273
+ tsType: attr.tsType,
274
+ source: attr.source === 'existing' ? 'Existing prop' : 'New attribute',
275
+ hasSuggestions: attr.suggestions.length > 0,
276
+ suggestions: attr.suggestions,
277
+ suggestedTypes: attr.suggestions.map((s) => s.type).join(', ') || 'string',
278
+ humanReadableName: toHumanReadableName(attr.name),
279
+ hasEnumSuggestion: attr.suggestions.some((s) => s.type === 'enum'),
280
+ })),
281
+ });
282
+ return {
283
+ content: [
284
+ {
285
+ type: 'text',
286
+ text: instructions,
287
+ },
288
+ ],
289
+ };
290
+ }
291
+ function handleConfigureRegionsStep(args, workspaceRoot) {
292
+ const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
293
+ const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
294
+ const instructions = pageDesignerDecoratorRules.getConfigureRegionsInstructions({
295
+ componentName: componentInfo.componentName,
296
+ });
297
+ return {
298
+ content: [
299
+ {
300
+ type: 'text',
301
+ text: instructions,
302
+ },
303
+ ],
304
+ };
305
+ }
306
+ function handleConfirmGenerationStep(args, workspaceRoot) {
307
+ const { componentMetadata, selectedProps = [], newAttributes = [], attributeConfig = {}, } = args.conversationContext || {};
308
+ if (!componentMetadata) {
309
+ return {
310
+ content: [
311
+ {
312
+ type: 'text',
313
+ text: 'Error: Missing component metadata. Please start from the beginning.',
314
+ },
315
+ ],
316
+ isError: true,
317
+ };
318
+ }
319
+ const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
320
+ const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
321
+ const attributes = [];
322
+ for (const propName of selectedProps) {
323
+ const prop = componentInfo.props.find((p) => p.name === propName);
324
+ if (!prop)
325
+ continue;
326
+ const config = attributeConfig[propName];
327
+ const hasConfig = config && Object.keys(config).length > 0;
328
+ attributes.push({
329
+ name: propName,
330
+ tsType: prop.type,
331
+ optional: prop.optional,
332
+ hasConfig,
333
+ config,
334
+ });
335
+ }
336
+ for (const attr of newAttributes) {
337
+ const config = attributeConfig[attr.name];
338
+ const hasConfig = config && Object.keys(config).length > 0;
339
+ attributes.push({
340
+ name: attr.name,
341
+ tsType: 'string',
342
+ optional: !attr.required,
343
+ hasConfig,
344
+ config,
345
+ });
346
+ }
347
+ const regionConfig = args.conversationContext?.regionConfig;
348
+ const hasRegions = regionConfig?.enabled && regionConfig.regions && regionConfig.regions.length > 0;
349
+ const context = {
350
+ needsImports: true,
351
+ componentId: componentMetadata.id,
352
+ componentName: componentMetadata.name,
353
+ componentDescription: componentMetadata.description,
354
+ componentGroup: componentMetadata.group || 'odyssey_base',
355
+ metadataClassName: `${componentInfo.componentName}Metadata`,
356
+ hasAttributes: attributes.length > 0,
357
+ hasRegions: hasRegions || false,
358
+ hasLoader: false,
359
+ regions: hasRegions ? regionConfig.regions || [] : [],
360
+ attributes,
361
+ };
362
+ const decoratorCode = generateDecoratorCode(context);
363
+ const userResponse = pageDesignerDecoratorRules.getConfirmGenerationInstructions({
364
+ decoratorCode,
365
+ componentName: componentInfo.componentName,
366
+ componentId: componentMetadata.id,
367
+ componentGroup: componentMetadata.group || 'odyssey_base',
368
+ file: args.component,
369
+ attributeCount: attributes.length,
370
+ hasRegions: hasRegions || false,
371
+ regionCount: hasRegions && regionConfig.regions ? regionConfig.regions.length : 0,
372
+ });
373
+ return {
374
+ content: [
375
+ {
376
+ type: 'text',
377
+ text: userResponse,
378
+ },
379
+ ],
380
+ };
381
+ }
382
+ /**
383
+ * Handle Auto Mode - Single-step decorator generation
384
+ *
385
+ * **Fully automated workflow:**
386
+ * 1. Analyzes component
387
+ * 2. Auto-selects suitable props (excludes complex and UI-only)
388
+ * 3. Auto-infers Page Designer types from naming patterns
389
+ * 4. Generates decorator code immediately
390
+ * 5. NO user interaction required
391
+ *
392
+ * **Selection criteria:**
393
+ * - ✅ Simple types (string, number, boolean)
394
+ * - ❌ Complex types (objects, functions, React nodes)
395
+ * - ❌ UI-only props (className, style, onClick, etc.)
396
+ *
397
+ * **Auto-configuration:**
398
+ * - High-confidence patterns get explicit types (url, image, enum)
399
+ * - Others use auto-inferred types
400
+ * - Human-readable names auto-generated
401
+ * - No regions configured (interactive mode for advanced features)
402
+ *
403
+ * **Use cases:**
404
+ * - Quick setup for standard components
405
+ * - Batch processing multiple components
406
+ * - Getting started quickly
407
+ *
408
+ * @internal
409
+ */
410
+ function handleAutoMode(args, workspaceRoot) {
411
+ const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
412
+ const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
413
+ if (componentInfo.hasDecorators) {
414
+ return {
415
+ content: [
416
+ {
417
+ type: 'text',
418
+ text: `# ⚠️ Component Already Decorated\n\nThe component \`${componentInfo.componentName}\` already has Page Designer decorators.\n\nWould you like to modify the existing decorators instead?`,
419
+ },
420
+ ],
421
+ };
422
+ }
423
+ const selectedProps = componentInfo.props.filter((p) => !p.isComplex && !p.isUIOnly).map((p) => p.name);
424
+ const attributeConfig = {};
425
+ const attributes = [];
426
+ for (const propName of selectedProps) {
427
+ const prop = componentInfo.props.find((p) => p.name === propName);
428
+ if (!prop)
429
+ continue;
430
+ const suggestions = generateTypeSuggestions(propName, prop.type);
431
+ const config = {
432
+ name: toHumanReadableName(propName),
433
+ };
434
+ const highPrioritySuggestion = suggestions.find((s) => s.priority === 'high');
435
+ if (highPrioritySuggestion) {
436
+ config.type = highPrioritySuggestion.type;
437
+ if (highPrioritySuggestion.type === 'enum') {
438
+ if (propName.toLowerCase().includes('size')) {
439
+ config.values = ['sm', 'default', 'lg'];
440
+ config.defaultValue = 'default';
441
+ }
442
+ else if (propName.toLowerCase().includes('variant')) {
443
+ config.values = ['default', 'primary', 'secondary'];
444
+ config.defaultValue = 'default';
445
+ }
446
+ }
447
+ if (highPrioritySuggestion.type === 'boolean') {
448
+ config.defaultValue = false;
449
+ }
450
+ }
451
+ if (Object.keys(config).length > 1) {
452
+ attributeConfig[propName] = config;
453
+ }
454
+ attributes.push({
455
+ name: propName,
456
+ tsType: prop.type,
457
+ optional: prop.optional,
458
+ hasConfig: Object.keys(config).length > 1,
459
+ config: Object.keys(config).length > 1 ? config : undefined,
460
+ });
461
+ }
462
+ const componentId = args.componentId || toKebabCase(componentInfo.componentName);
463
+ const componentName = toHumanReadableName(componentInfo.componentName);
464
+ const componentDescription = `${componentName} component for Page Designer`;
465
+ const context = {
466
+ needsImports: true,
467
+ componentId,
468
+ componentName,
469
+ componentDescription,
470
+ componentGroup: 'odyssey_base',
471
+ metadataClassName: `${componentInfo.componentName}Metadata`,
472
+ hasAttributes: attributes.length > 0,
473
+ hasRegions: false,
474
+ hasLoader: false,
475
+ regions: [],
476
+ attributes,
477
+ };
478
+ const decoratorCode = generateDecoratorCode(context);
479
+ const response = pageDesignerDecoratorRules.getAutoModeInstructions({
480
+ componentName: componentInfo.componentName,
481
+ file: args.component,
482
+ componentId,
483
+ selectedPropCount: selectedProps.length,
484
+ autoConfigCount: Object.keys(attributeConfig).length,
485
+ autoInferredCount: selectedProps.length - Object.keys(attributeConfig).length,
486
+ hasNoSuitableProps: selectedProps.length === 0,
487
+ selectedProps: selectedProps.length > 0 ? selectedProps.map((p) => `\`${p}\``).join(', ') : 'None',
488
+ decoratorCode,
489
+ componentGroup: 'odyssey_base',
490
+ });
491
+ return {
492
+ content: [
493
+ {
494
+ type: 'text',
495
+ text: response,
496
+ },
497
+ ],
498
+ };
499
+ }
500
+ // ============================================================================
501
+ // TOOL EXPORT
502
+ // ============================================================================
503
+ /**
504
+ * Creates the Page Designer decorator tool for Storefront Next.
505
+ *
506
+ * @param loadServices - Function that loads configuration and returns Services instance
507
+ * @returns The configured MCP tool
508
+ */
509
+ export function createPageDesignerDecoratorTool(loadServices) {
510
+ return {
511
+ name: 'storefront_next_page_designer_decorator',
512
+ description: 'Adds Page Designer decorators (@Component, @AttributeDefinition, @RegionDefinition) to React components. ' +
513
+ 'Two modes: autoMode=true for quick setup with defaults, or interactive mode via conversationContext.step. ' +
514
+ 'Component discovery uses workingDirectory from flags/env. ' +
515
+ 'Auto mode: selects suitable props, infers types, generates code immediately. ' +
516
+ 'Interactive mode: multi-step workflow (analyze → select_props → configure_attrs → configure_regions → confirm_generation).',
517
+ inputSchema: pageDesignerDecoratorSchema.shape,
518
+ toolsets: ['STOREFRONTNEXT'],
519
+ isGA: false,
520
+ async handler(args) {
521
+ try {
522
+ // Validate and parse input
523
+ const validatedArgs = pageDesignerDecoratorSchema.parse(args);
524
+ // Use workingDirectory from services to ensure we search in the correct project directory
525
+ // This prevents searches in the home folder when MCP clients spawn servers from ~
526
+ const services = loadServices();
527
+ const workspaceRoot = services.getWorkingDirectory();
528
+ if (validatedArgs.autoMode === undefined && !validatedArgs.conversationContext) {
529
+ const fullPath = resolveComponent(validatedArgs.component, workspaceRoot, validatedArgs.searchPaths);
530
+ const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
531
+ const instructions = pageDesignerDecoratorRules.getModeSelectionInstructions({
532
+ componentName: componentInfo.componentName,
533
+ file: validatedArgs.component,
534
+ });
535
+ return {
536
+ content: [
537
+ {
538
+ type: 'text',
539
+ text: instructions,
540
+ },
541
+ ],
542
+ };
543
+ }
544
+ if (validatedArgs.autoMode) {
545
+ return handleAutoMode(validatedArgs, workspaceRoot);
546
+ }
547
+ const step = validatedArgs.conversationContext?.step || 'analyze';
548
+ switch (step) {
549
+ case 'analyze': {
550
+ return handleAnalyzeStep(validatedArgs, workspaceRoot);
551
+ }
552
+ case 'configure_attrs': {
553
+ return handleConfigureAttrsStep(validatedArgs, workspaceRoot);
554
+ }
555
+ case 'configure_regions': {
556
+ return handleConfigureRegionsStep(validatedArgs, workspaceRoot);
557
+ }
558
+ case 'confirm_generation': {
559
+ return handleConfirmGenerationStep(validatedArgs, workspaceRoot);
560
+ }
561
+ case 'select_props': {
562
+ return handleSelectPropsStep(validatedArgs, workspaceRoot);
563
+ }
564
+ default: {
565
+ const unknownStep = step;
566
+ throw new Error(`Unknown step: ${unknownStep}`);
567
+ }
568
+ }
569
+ }
570
+ catch (error) {
571
+ const errorMessage = error instanceof Error ? error.message : String(error);
572
+ // Check if it's a Zod validation error
573
+ if (error instanceof Error && error.name === 'ZodError') {
574
+ return {
575
+ content: [
576
+ {
577
+ type: 'text',
578
+ text: `# Error: Invalid Input\n\n${errorMessage}\n\nPlease check your input parameters and try again.`,
579
+ },
580
+ ],
581
+ isError: true,
582
+ };
583
+ }
584
+ return {
585
+ content: [
586
+ {
587
+ type: 'text',
588
+ text: `# Error Adding Page Designer Support\n\n${errorMessage}`,
589
+ },
590
+ ],
591
+ isError: true,
592
+ };
593
+ }
594
+ },
595
+ };
596
+ }
597
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Mode selection rule - Entry point for Page Designer decorator tool
3
+ */
4
+ export interface ModeSelectionContext {
5
+ componentName: string;
6
+ file: string;
7
+ }
8
+ export declare function renderModeSelection(context: ModeSelectionContext): string;
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ export function renderModeSelection(context) {
7
+ return `# 🎯 Choose Page Designer Setup Mode
8
+
9
+ I need to know which mode you'd like to use for adding Page Designer support to **\`${context.componentName}\`**.
10
+
11
+ ## Available Modes
12
+
13
+ ### 🤖 Auto Mode (Quick & Automatic)
14
+ - **Best for**: Quick setup, standard components, batch processing
15
+ - **What happens**:
16
+ - Automatically analyzes the component
17
+ - Auto-selects suitable props (excludes complex types)
18
+ - Auto-infers types based on naming patterns
19
+ - Generates decorators immediately with sensible defaults
20
+ - **No confirmation needed** - code generated instantly
21
+ - **Time**: ~1 step
22
+ - **Control**: Low (uses smart defaults)
23
+
24
+ ### 👤 Interactive Mode (Step-by-Step)
25
+ - **Best for**: Complex components, custom requirements, learning the process
26
+ - **What happens**:
27
+ - Multi-step workflow with your input at each stage
28
+ - Review and approve prop selections
29
+ - Configure attribute types, names, and defaults
30
+ - Configure regions for nested content (optional)
31
+ - **Requires confirmation** before generating code
32
+ - **Time**: ~4-5 steps
33
+ - **Control**: High (you decide everything)
34
+
35
+ ## ⚡ How to Proceed
36
+
37
+ **⚠️ IMPORTANT: WAIT for the user to choose a mode. DO NOT proceed automatically.**
38
+
39
+ Please ask the user: **"Which mode would you like to use: Auto Mode or Interactive Mode?"**
40
+
41
+ Once the user responds:
42
+
43
+ **For Auto Mode**, call the tool again with:
44
+ \`\`\`json
45
+ {
46
+ "file": "${context.file}",
47
+ "autoMode": true
48
+ }
49
+ \`\`\`
50
+
51
+ **For Interactive Mode**, call the tool again with:
52
+ \`\`\`json
53
+ {
54
+ "file": "${context.file}",
55
+ "conversationContext": {
56
+ "step": "analyze"
57
+ }
58
+ }
59
+ \`\`\`
60
+
61
+ ---
62
+
63
+ 💡 **Tip**: If unsure, try **Auto Mode** first. You can always modify the generated decorators later or rerun in Interactive Mode for more control.`;
64
+ }
65
+ //# sourceMappingURL=1-mode-selection.js.map
@@ -0,0 +1,13 @@
1
+ export interface AutoModeContext {
2
+ componentName: string;
3
+ file: string;
4
+ componentId: string;
5
+ selectedPropCount: number;
6
+ autoConfigCount: number;
7
+ autoInferredCount: number;
8
+ hasNoSuitableProps: boolean;
9
+ selectedProps: string;
10
+ decoratorCode: string;
11
+ componentGroup: string;
12
+ }
13
+ export declare function renderAutoMode(context: AutoModeContext): string;