@aifabrix/builder 2.32.2 → 2.33.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 (130) hide show
  1. package/.cursor/rules/project-rules.mdc +8 -0
  2. package/README.md +36 -8
  3. package/bin/aifabrix.js +6 -8
  4. package/integration/hubspot/README.md +8 -7
  5. package/integration/hubspot/companies.json +2048 -0
  6. package/integration/hubspot/create-hubspot.js +665 -0
  7. package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
  8. package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
  9. package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
  10. package/integration/hubspot/hubspot-deploy.json +832 -81
  11. package/integration/hubspot/hubspot-system.json +99 -0
  12. package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
  13. package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
  14. package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
  15. package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
  16. package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
  17. package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
  18. package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
  19. package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
  20. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  21. package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
  22. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
  23. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
  24. package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
  25. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
  26. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  27. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  28. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
  29. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
  30. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
  31. package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
  32. package/integration/hubspot/test-dataplane-down-tests.js +419 -0
  33. package/integration/hubspot/test-dataplane-down.js +157 -0
  34. package/integration/hubspot/test.js +1517 -0
  35. package/integration/hubspot/variables.yaml +4 -4
  36. package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
  37. package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
  38. package/lib/api/applications.api.js +1 -0
  39. package/lib/api/index.js +10 -5
  40. package/lib/api/types/wizard.types.js +176 -38
  41. package/lib/api/wizard.api.js +207 -38
  42. package/lib/app/deploy.js +116 -54
  43. package/lib/app/display.js +6 -5
  44. package/lib/app/dockerfile.js +2 -1
  45. package/lib/app/list.js +78 -37
  46. package/lib/app/prompts.js +9 -5
  47. package/lib/app/readme.js +41 -112
  48. package/lib/app/register.js +44 -9
  49. package/lib/app/rotate-secret.js +50 -32
  50. package/lib/cli.js +243 -65
  51. package/lib/commands/app.js +4 -9
  52. package/lib/commands/auth-config.js +125 -0
  53. package/lib/commands/auth-status.js +261 -0
  54. package/lib/commands/datasource.js +3 -6
  55. package/lib/commands/login-credentials.js +4 -4
  56. package/lib/commands/login-device.js +43 -29
  57. package/lib/commands/login.js +22 -13
  58. package/lib/commands/wizard-config-normalizer.js +92 -0
  59. package/lib/commands/wizard-core.js +515 -0
  60. package/lib/commands/wizard-dataplane.js +122 -0
  61. package/lib/commands/wizard-headless.js +115 -0
  62. package/lib/commands/wizard.js +129 -357
  63. package/lib/core/config.js +46 -0
  64. package/lib/core/secrets.js +3 -22
  65. package/lib/core/templates-env.js +1 -1
  66. package/lib/datasource/deploy.js +34 -23
  67. package/lib/datasource/list.js +8 -6
  68. package/lib/deployment/deployer.js +25 -0
  69. package/lib/deployment/environment.js +10 -13
  70. package/lib/external-system/delete.js +151 -0
  71. package/lib/external-system/deploy.js +54 -378
  72. package/lib/external-system/download-helpers.js +45 -65
  73. package/lib/external-system/download.js +34 -13
  74. package/lib/external-system/generator.js +11 -7
  75. package/lib/external-system/test-auth.js +5 -3
  76. package/lib/generator/builders.js +3 -1
  77. package/lib/generator/external-controller-manifest.js +157 -0
  78. package/lib/generator/external-schema-utils.js +236 -0
  79. package/lib/generator/external.js +55 -3
  80. package/lib/generator/index.js +22 -10
  81. package/lib/generator/wizard-prompts.js +33 -10
  82. package/lib/generator/wizard.js +69 -86
  83. package/lib/infrastructure/compose.js +100 -0
  84. package/lib/infrastructure/helpers.js +139 -0
  85. package/lib/infrastructure/index.js +52 -311
  86. package/lib/infrastructure/services.js +168 -0
  87. package/lib/schema/application-schema.json +24 -5
  88. package/lib/schema/external-datasource.schema.json +303 -17
  89. package/lib/schema/external-system.schema.json +1 -1
  90. package/lib/schema/wizard-config.schema.json +234 -0
  91. package/lib/utils/api.js +37 -42
  92. package/lib/utils/app-existence.js +42 -0
  93. package/lib/utils/app-register-config.js +7 -2
  94. package/lib/utils/app-register-display.js +2 -1
  95. package/lib/utils/auth-config-validator.js +92 -0
  96. package/lib/utils/cli-utils.js +3 -1
  97. package/lib/utils/command-header.js +43 -0
  98. package/lib/utils/compose-generator.js +113 -70
  99. package/lib/utils/controller-url.js +115 -0
  100. package/lib/utils/dataplane-health.js +115 -0
  101. package/lib/utils/dataplane-resolver.js +29 -0
  102. package/lib/utils/dev-config.js +6 -2
  103. package/lib/utils/env-copy.js +2 -1
  104. package/lib/utils/env-map.js +2 -1
  105. package/lib/utils/env-ports.js +2 -1
  106. package/lib/utils/env-template.js +1 -1
  107. package/lib/utils/error-formatter.js +149 -28
  108. package/lib/utils/external-readme.js +125 -0
  109. package/lib/utils/help-builder.js +190 -0
  110. package/lib/utils/infra-status.js +13 -3
  111. package/lib/utils/paths.js +17 -2
  112. package/lib/utils/port-resolver.js +111 -0
  113. package/lib/utils/secrets-helpers.js +3 -15
  114. package/lib/utils/secrets-utils.js +2 -2
  115. package/lib/utils/token-manager.js +69 -4
  116. package/lib/utils/variable-transformer.js +7 -2
  117. package/lib/validation/external-manifest-validator.js +202 -0
  118. package/lib/validation/validate-display.js +406 -0
  119. package/lib/validation/validate.js +159 -123
  120. package/lib/validation/validator.js +38 -4
  121. package/lib/validation/wizard-config-validator.js +267 -0
  122. package/package.json +4 -2
  123. package/templates/applications/README.md.hbs +19 -17
  124. package/templates/applications/miso-controller/env.template +1 -1
  125. package/templates/applications/miso-controller/rbac.yaml +7 -7
  126. package/templates/external-system/README.md.hbs +99 -0
  127. package/templates/external-system/external-system.json.hbs +1 -1
  128. package/templates/infra/compose.yaml.hbs +35 -0
  129. package/templates/python/docker-compose.hbs +26 -0
  130. package/templates/typescript/docker-compose.hbs +26 -0
@@ -1,28 +1,14 @@
1
1
  /**
2
- * @fileoverview Wizard command handler - interactive external system creation
2
+ * @fileoverview Wizard command handler - interactive and headless external system creation
3
3
  * @author AI Fabrix Team
4
4
  * @version 2.0.0
5
5
  */
6
6
 
7
7
  const chalk = require('chalk');
8
- const ora = require('ora');
9
- const path = require('path');
10
- const fs = require('fs').promises;
11
8
  const logger = require('../utils/logger');
12
- const config = require('../core/config');
13
- const { getDeploymentAuth } = require('../utils/token-manager');
14
- const { getDataplaneUrl } = require('../datasource/deploy');
15
- const {
16
- selectMode,
17
- selectSource,
18
- parseOpenApi,
19
- detectType,
20
- generateConfig,
21
- validateWizardConfig,
22
- getDeploymentDocs
23
- } = require('../api/wizard.api');
24
9
  const {
25
10
  promptForMode,
11
+ promptForSystemIdOrKey,
26
12
  promptForSourceType,
27
13
  promptForOpenApiFile,
28
14
  promptForOpenApiUrl,
@@ -33,282 +19,148 @@ const {
33
19
  promptForConfigReview,
34
20
  promptForAppName
35
21
  } = require('../generator/wizard-prompts');
36
- const { generateWizardFiles } = require('../generator/wizard');
22
+ const {
23
+ validateAndCheckAppDirectory,
24
+ handleOpenApiParsing,
25
+ handleCredentialSelection,
26
+ handleTypeDetection,
27
+ handleConfigurationGeneration,
28
+ validateWizardConfiguration,
29
+ handleFileSaving,
30
+ setupDataplaneAndAuth
31
+ } = require('./wizard-core');
32
+ const { handleWizardHeadless } = require('./wizard-headless');
33
+ const { createWizardSession, updateWizardSession } = require('../api/wizard.api');
37
34
 
38
35
  /**
39
- * Validate app name and check if directory exists
40
- * @async
41
- * @function validateAndCheckAppDirectory
42
- * @param {string} appName - Application name
43
- * @returns {Promise<boolean>} True if should continue, false if cancelled
44
- * @throws {Error} If validation fails
36
+ * Extract session ID from response data
37
+ * @function extractSessionId
38
+ * @param {Object} responseData - Response data from API
39
+ * @returns {string} Session ID
40
+ * @throws {Error} If session ID not found or invalid
45
41
  */
46
- async function validateAndCheckAppDirectory(appName) {
47
- // Validate app name
48
- if (!/^[a-z0-9-_]+$/.test(appName)) {
49
- throw new Error('Application name must contain only lowercase letters, numbers, hyphens, and underscores');
42
+ function extractSessionId(responseData) {
43
+ let sessionId = responseData?.data?.sessionId || responseData?.sessionId ||
44
+ responseData?.data?.session_id || responseData?.session_id;
45
+ if (sessionId && typeof sessionId === 'object') {
46
+ sessionId = sessionId.id || sessionId.sessionId || sessionId.session_id;
50
47
  }
51
-
52
- // Check if app directory already exists
53
- const appPath = path.join(process.cwd(), 'integration', appName);
54
- try {
55
- await fs.access(appPath);
56
- const { overwrite } = await require('inquirer').prompt([
57
- {
58
- type: 'confirm',
59
- name: 'overwrite',
60
- message: `Directory ${appPath} already exists. Overwrite?`,
61
- default: false
62
- }
63
- ]);
64
- if (!overwrite) {
65
- logger.log(chalk.yellow('Wizard cancelled.'));
66
- return false;
67
- }
68
- } catch (error) {
69
- // Directory doesn't exist, continue
70
- if (error.code !== 'ENOENT') {
71
- throw error;
72
- }
48
+ if (!sessionId || typeof sessionId !== 'string') {
49
+ throw new Error(`Session ID not found: ${JSON.stringify(responseData, null, 2)}`);
73
50
  }
74
- return true;
51
+ return sessionId;
75
52
  }
76
53
 
77
54
  /**
78
- * Handle mode selection step
55
+ * Handle interactive mode selection step
79
56
  * @async
80
- * @function handleModeSelection
57
+ * @function handleInteractiveModeSelection
81
58
  * @param {string} dataplaneUrl - Dataplane URL
82
59
  * @param {Object} authConfig - Authentication configuration
83
- * @returns {Promise<string>} Selected mode
84
- * @throws {Error} If mode selection fails
60
+ * @returns {Promise<Object>} Object with mode and sessionId
85
61
  */
86
- async function handleModeSelection(dataplaneUrl, authConfig) {
87
- logger.log(chalk.blue('\n📋 Step 1: Mode Selection'));
62
+ async function handleInteractiveModeSelection(dataplaneUrl, authConfig) {
63
+ logger.log(chalk.blue('\n\uD83D\uDCCB Step 1: Mode Selection'));
88
64
  const mode = await promptForMode();
89
- const modeResponse = await selectMode(dataplaneUrl, authConfig, mode);
90
- if (!modeResponse.success) {
91
- throw new Error(`Mode selection failed: ${modeResponse.error || modeResponse.formattedError}`);
65
+ let systemIdOrKey = null;
66
+ if (mode === 'add-datasource') {
67
+ systemIdOrKey = await promptForSystemIdOrKey();
92
68
  }
93
- return mode;
69
+ const sessionResponse = await createWizardSession(dataplaneUrl, authConfig, mode, systemIdOrKey);
70
+ if (!sessionResponse.success || !sessionResponse.data) {
71
+ const errorMsg = sessionResponse.formattedError || sessionResponse.error ||
72
+ sessionResponse.errorData?.detail || sessionResponse.message ||
73
+ (sessionResponse.status ? `HTTP ${sessionResponse.status}` : 'Unknown error');
74
+ throw new Error(`Failed to create wizard session: ${errorMsg}`);
75
+ }
76
+ const sessionId = extractSessionId(sessionResponse.data);
77
+ return { mode, sessionId, systemIdOrKey };
94
78
  }
95
79
 
96
80
  /**
97
- * Handle source selection step
81
+ * Handle interactive source selection step
98
82
  * @async
99
- * @function handleSourceSelection
83
+ * @function handleInteractiveSourceSelection
100
84
  * @param {string} dataplaneUrl - Dataplane URL
85
+ * @param {string} sessionId - Wizard session ID
101
86
  * @param {Object} authConfig - Authentication configuration
102
87
  * @returns {Promise<Object>} Object with sourceType and sourceData
103
- * @throws {Error} If source selection fails
104
88
  */
105
- async function handleSourceSelection(dataplaneUrl, authConfig) {
106
- logger.log(chalk.blue('\n📋 Step 2: Source Selection'));
89
+ async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig) {
90
+ logger.log(chalk.blue('\n\uD83D\uDCCB Step 2: Source Selection'));
107
91
  const sourceType = await promptForSourceType();
108
92
  let sourceData = null;
93
+ const updateData = { currentStep: 1 };
109
94
 
110
95
  if (sourceType === 'openapi-file') {
111
- const filePath = await promptForOpenApiFile();
112
- sourceData = filePath;
96
+ sourceData = await promptForOpenApiFile();
113
97
  } else if (sourceType === 'openapi-url') {
114
- const url = await promptForOpenApiUrl();
115
- sourceData = url;
98
+ sourceData = await promptForOpenApiUrl();
99
+ updateData.openapiSpec = null;
116
100
  } else if (sourceType === 'mcp-server') {
117
101
  const mcpDetails = await promptForMcpServer();
118
102
  sourceData = JSON.stringify(mcpDetails);
103
+ updateData.mcpServerUrl = mcpDetails.url || null;
119
104
  } else if (sourceType === 'known-platform') {
120
- const platform = await promptForKnownPlatform();
121
- sourceData = platform;
105
+ sourceData = await promptForKnownPlatform();
122
106
  }
123
107
 
124
- const sourceResponse = await selectSource(dataplaneUrl, authConfig, sourceType, sourceData);
125
- if (!sourceResponse.success) {
126
- throw new Error(`Source selection failed: ${sourceResponse.error || sourceResponse.formattedError}`);
108
+ const updateResponse = await updateWizardSession(dataplaneUrl, sessionId, authConfig, updateData);
109
+ if (!updateResponse.success) {
110
+ throw new Error(`Source selection failed: ${updateResponse.error || updateResponse.formattedError}`);
127
111
  }
128
112
 
129
113
  return { sourceType, sourceData };
130
114
  }
131
115
 
132
116
  /**
133
- * Parse OpenAPI file
134
- * @async
135
- * @function parseOpenApiFile
136
- * @param {string} dataplaneUrl - Dataplane URL
137
- * @param {Object} authConfig - Authentication configuration
138
- * @param {string} sourceData - Source data (file path)
139
- * @returns {Promise<Object>} OpenAPI spec
140
- * @throws {Error} If parsing fails
141
- */
142
- async function parseOpenApiFile(dataplaneUrl, authConfig, sourceData) {
143
- logger.log(chalk.blue('\n📋 Step 3: Parsing OpenAPI File'));
144
- const spinner = ora('Parsing OpenAPI file...').start();
145
- try {
146
- const parseResponse = await parseOpenApi(dataplaneUrl, authConfig, sourceData);
147
- spinner.stop();
148
- if (!parseResponse.success) {
149
- throw new Error(`OpenAPI parsing failed: ${parseResponse.error || parseResponse.formattedError}`);
150
- }
151
- logger.log(chalk.green('✓ OpenAPI file parsed successfully'));
152
- return parseResponse.data?.spec;
153
- } catch (error) {
154
- spinner.stop();
155
- throw error;
156
- }
157
- }
158
-
159
- /**
160
- * Handle OpenAPI parsing step
161
- * @async
162
- * @function handleOpenApiParsing
163
- * @param {string} dataplaneUrl - Dataplane URL
164
- * @param {Object} authConfig - Authentication configuration
165
- * @param {string} sourceType - Source type
166
- * @param {string} sourceData - Source data
167
- * @returns {Promise<Object|null>} OpenAPI spec or null
168
- * @throws {Error} If parsing fails
169
- */
170
- async function handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData) {
171
- if (sourceType === 'openapi-file') {
172
- return await parseOpenApiFile(dataplaneUrl, authConfig, sourceData);
173
- }
174
- if (sourceType === 'openapi-url') {
175
- logger.log(chalk.blue('\n📋 Step 3: Parsing OpenAPI URL'));
176
- logger.log(chalk.green('✓ OpenAPI URL processed'));
177
- return null;
178
- }
179
- return null;
180
- }
181
-
182
- /**
183
- * Handle type detection step
184
- * @async
185
- * @function handleTypeDetection
186
- * @param {string} dataplaneUrl - Dataplane URL
187
- * @param {Object} authConfig - Authentication configuration
188
- * @param {Object} openApiSpec - OpenAPI specification
189
- * @returns {Promise<Object|null>} Detected type or null
190
- */
191
- async function handleTypeDetection(dataplaneUrl, authConfig, openApiSpec) {
192
- if (!openApiSpec) {
193
- return null;
194
- }
195
-
196
- logger.log(chalk.blue('\n📋 Step 4: Detecting API Type'));
197
- const spinner = ora('Detecting API type...').start();
198
- try {
199
- const detectResponse = await detectType(dataplaneUrl, authConfig, openApiSpec);
200
- spinner.stop();
201
- if (detectResponse.success && detectResponse.data) {
202
- const detectedType = detectResponse.data;
203
- logger.log(chalk.green(`✓ API type detected: ${detectedType.apiType || 'unknown'}`));
204
- if (detectedType.category) {
205
- logger.log(chalk.gray(` Category: ${detectedType.category}`));
206
- }
207
- return detectedType;
208
- }
209
- } catch (error) {
210
- spinner.stop();
211
- logger.log(chalk.yellow(`⚠ Type detection failed: ${error.message}`));
212
- }
213
- return null;
214
- }
215
-
216
- /**
217
- * Handle configuration generation step
117
+ * Handle interactive configuration generation step
218
118
  * @async
219
- * @function handleConfigurationGeneration
220
- * @param {string} dataplaneUrl - Dataplane URL
221
- * @param {Object} authConfig - Authentication configuration
222
- * @param {string} mode - Selected mode
223
- * @param {string} sourceType - Source type
224
- * @param {Object} openApiSpec - OpenAPI specification
225
- * @returns {Promise<Object>} Generated configuration with systemConfig, datasourceConfigs, and systemKey
226
- * @throws {Error} If generation fails
119
+ * @function handleInteractiveConfigGeneration
120
+ * @param {Object} options - Configuration options
121
+ * @param {string} options.dataplaneUrl - Dataplane URL
122
+ * @param {Object} options.authConfig - Authentication configuration
123
+ * @param {string} options.mode - Selected mode
124
+ * @param {Object} options.openapiSpec - OpenAPI specification
125
+ * @param {Object} options.detectedType - Detected type info
126
+ * @param {string} [options.credentialIdOrKey] - Credential ID or key (optional)
127
+ * @param {string} [options.systemIdOrKey] - System ID or key (optional)
128
+ * @returns {Promise<Object>} Generated configuration
227
129
  */
228
- async function handleConfigurationGeneration(dataplaneUrl, authConfig, mode, sourceType, openApiSpec) {
229
- logger.log(chalk.blue('\n📋 Step 5: User Preferences'));
130
+ async function handleInteractiveConfigGeneration(options) {
131
+ logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: User Preferences'));
230
132
  const userIntent = await promptForUserIntent();
231
133
  const preferences = await promptForUserPreferences();
232
134
 
233
- logger.log(chalk.blue('\n📋 Step 6: Generating Configuration'));
234
- const spinner = ora('Generating configuration via AI (this may take 10-30 seconds)...').start();
235
- try {
236
- const generateResponse = await generateConfig(dataplaneUrl, authConfig, {
237
- mode,
238
- sourceType,
239
- openApiSpec,
240
- userIntent,
241
- preferences
242
- });
243
- spinner.stop();
244
- if (!generateResponse.success) {
245
- throw new Error(`Configuration generation failed: ${generateResponse.error || generateResponse.formattedError}`);
246
- }
247
-
248
- const systemConfig = generateResponse.data?.systemConfig;
249
- const datasourceConfigs = generateResponse.data?.datasourceConfigs || [];
250
- const systemKey = generateResponse.data?.systemKey;
251
-
252
- if (!systemConfig) {
253
- throw new Error('System configuration not found in generation response');
254
- }
255
-
256
- logger.log(chalk.green('✓ Configuration generated successfully'));
257
- return { systemConfig, datasourceConfigs, systemKey };
258
- } catch (error) {
259
- spinner.stop();
260
- throw error;
261
- }
262
- }
263
-
264
- /**
265
- * Validate wizard configuration
266
- * @async
267
- * @function validateWizardConfiguration
268
- * @param {string} dataplaneUrl - Dataplane URL
269
- * @param {Object} authConfig - Authentication configuration
270
- * @param {Object} systemConfig - System configuration
271
- * @param {Object[]} datasourceConfigs - Datasource configurations
272
- * @throws {Error} If validation fails
273
- */
274
- async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
275
- const validateSpinner = ora('Validating configuration...').start();
276
- try {
277
- const validateResponse = await validateWizardConfig(dataplaneUrl, authConfig, systemConfig, datasourceConfigs);
278
- validateSpinner.stop();
279
- if (!validateResponse.success || !validateResponse.data?.valid) {
280
- const errors = validateResponse.data?.errors || [];
281
- const errorMsg = errors.length > 0
282
- ? errors.map(e => e.message || e).join(', ')
283
- : validateResponse.error || validateResponse.formattedError || 'Validation failed';
284
- throw new Error(`Configuration validation failed: ${errorMsg}`);
285
- }
286
- logger.log(chalk.green('✓ Configuration validated successfully'));
287
- if (validateResponse.data?.warnings && validateResponse.data.warnings.length > 0) {
288
- logger.log(chalk.yellow('\n⚠ Warnings:'));
289
- validateResponse.data.warnings.forEach(warning => {
290
- logger.log(chalk.yellow(` - ${warning.message || warning}`));
291
- });
292
- }
293
- } catch (error) {
294
- validateSpinner.stop();
295
- throw error;
296
- }
135
+ const configPrefs = {
136
+ intent: userIntent,
137
+ enableMCP: preferences.mcp,
138
+ enableABAC: preferences.abac,
139
+ enableRBAC: preferences.rbac
140
+ };
141
+
142
+ return await handleConfigurationGeneration(options.dataplaneUrl, options.authConfig, {
143
+ mode: options.mode,
144
+ openapiSpec: options.openapiSpec,
145
+ detectedType: options.detectedType,
146
+ configPrefs,
147
+ credentialIdOrKey: options.credentialIdOrKey,
148
+ systemIdOrKey: options.systemIdOrKey
149
+ });
297
150
  }
298
151
 
299
152
  /**
300
- * Handle configuration review and validation step
153
+ * Handle configuration review and validation step (interactive mode only)
301
154
  * @async
302
155
  * @function handleConfigurationReview
303
156
  * @param {string} dataplaneUrl - Dataplane URL
304
157
  * @param {Object} authConfig - Authentication configuration
305
158
  * @param {Object} systemConfig - System configuration
306
159
  * @param {Object[]} datasourceConfigs - Datasource configurations
307
- * @returns {Promise<Object>} Final configurations with systemConfig and datasourceConfigs
308
- * @throws {Error} If validation fails
160
+ * @returns {Promise<Object>} Final configurations
309
161
  */
310
162
  async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
311
- logger.log(chalk.blue('\n📋 Step 7: Review & Validate'));
163
+ logger.log(chalk.blue('\n\uD83D\uDCCB Step 6-7: Review & Validate'));
312
164
  const reviewResult = await promptForConfigReview(systemConfig, datasourceConfigs);
313
165
 
314
166
  if (reviewResult.action === 'cancel') {
@@ -316,97 +168,55 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig,
316
168
  return null;
317
169
  }
318
170
 
319
- // Use edited configs if user edited them
320
171
  const finalSystemConfig = reviewResult.systemConfig || systemConfig;
321
172
  const finalDatasourceConfigs = reviewResult.datasourceConfigs || datasourceConfigs;
322
173
 
323
- // Validate configuration
324
174
  await validateWizardConfiguration(dataplaneUrl, authConfig, finalSystemConfig, finalDatasourceConfigs);
325
175
 
326
176
  return { systemConfig: finalSystemConfig, datasourceConfigs: finalDatasourceConfigs };
327
177
  }
328
178
 
329
179
  /**
330
- * Handle file saving step
331
- * @async
332
- * @function handleFileSaving
333
- * @param {string} appName - Application name
334
- * @param {Object} systemConfig - System configuration
335
- * @param {Object[]} datasourceConfigs - Datasource configurations
336
- * @param {string} systemKey - System key
337
- * @param {string} dataplaneUrl - Dataplane URL
338
- * @param {Object} authConfig - Authentication configuration
339
- * @returns {Promise<Object>} Generated files information
340
- * @throws {Error} If file saving fails
341
- */
342
- async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey, dataplaneUrl, authConfig) {
343
- logger.log(chalk.blue('\n📋 Step 8: Saving Files'));
344
- const saveSpinner = ora('Saving files...').start();
345
- try {
346
- let aiGeneratedReadme = null;
347
- if (systemKey && dataplaneUrl && authConfig) {
348
- try {
349
- const docsResponse = await getDeploymentDocs(dataplaneUrl, authConfig, systemKey);
350
- if (docsResponse.success && docsResponse.data?.content) {
351
- aiGeneratedReadme = docsResponse.data.content;
352
- logger.log(chalk.gray(' ✓ Fetched AI-generated README.md from dataplane'));
353
- }
354
- } catch (error) {
355
- logger.log(chalk.gray(` ⚠ Could not fetch AI-generated README: ${error.message}`));
356
- }
357
- }
358
- const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme });
359
- saveSpinner.stop();
360
- logger.log(chalk.green('\n✓ Wizard completed successfully!'));
361
- logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
362
- logger.log(chalk.blue('\nNext steps:'));
363
- [` 1. Review the generated files in integration/${appName}/`, ' 2. Update env.template with your authentication details', ` 3. Deploy using: ./deploy.sh or .\\deploy.ps1 (or aifabrix deploy ${appName})`].forEach(step => logger.log(chalk.gray(step)));
364
- return generatedFiles;
365
- } catch (error) {
366
- saveSpinner.stop();
367
- throw error;
368
- }
369
- }
370
-
371
- /**
372
- * Execute wizard flow steps
180
+ * Execute wizard flow steps (interactive mode)
373
181
  * @async
374
182
  * @function executeWizardFlow
375
183
  * @param {string} appName - Application name
376
184
  * @param {string} dataplaneUrl - Dataplane URL
377
185
  * @param {Object} authConfig - Authentication configuration
378
186
  * @returns {Promise<void>} Resolves when wizard flow completes
379
- * @throws {Error} If wizard flow fails
380
187
  */
381
188
  async function executeWizardFlow(appName, dataplaneUrl, authConfig) {
382
189
  // Step 1: Mode Selection
383
- const mode = await handleModeSelection(dataplaneUrl, authConfig);
190
+ const { mode, sessionId, systemIdOrKey } = await handleInteractiveModeSelection(dataplaneUrl, authConfig);
384
191
 
385
192
  // Step 2: Source Selection
386
- const { sourceType, sourceData } = await handleSourceSelection(dataplaneUrl, authConfig);
193
+ const { sourceType, sourceData } = await handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig);
387
194
 
388
- // Step 3: Parse OpenAPI (if applicable)
389
- const openApiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
195
+ // Parse OpenAPI (part of step 2)
196
+ const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
390
197
 
391
- // Step 4: Detect Type (optional, if OpenAPI spec available)
392
- await handleTypeDetection(dataplaneUrl, authConfig, openApiSpec);
198
+ // Step 3: Credential Selection (optional)
199
+ const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig);
393
200
 
394
- // Step 5-6: Generate Configuration
395
- const { systemConfig, datasourceConfigs, systemKey } = await handleConfigurationGeneration(
201
+ // Step 4: Detect Type
202
+ const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
203
+
204
+ // Step 5: Generate Configuration
205
+ const { systemConfig, datasourceConfigs, systemKey } = await handleInteractiveConfigGeneration({
396
206
  dataplaneUrl,
397
207
  authConfig,
398
208
  mode,
399
- sourceType,
400
- openApiSpec
401
- );
209
+ openapiSpec,
210
+ detectedType,
211
+ credentialIdOrKey,
212
+ systemIdOrKey
213
+ });
402
214
 
403
- // Step 7: Review & Validate
215
+ // Step 6-7: Review & Validate
404
216
  const finalConfigs = await handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs);
405
- if (!finalConfigs) {
406
- return; // User cancelled
407
- }
217
+ if (!finalConfigs) return;
408
218
 
409
- // Step 8: Save Files
219
+ // Step 7: Save Files
410
220
  await handleFileSaving(
411
221
  appName,
412
222
  finalConfigs.systemConfig,
@@ -426,73 +236,35 @@ async function executeWizardFlow(appName, dataplaneUrl, authConfig) {
426
236
  * @param {string} [options.controller] - Controller URL
427
237
  * @param {string} [options.environment] - Environment key
428
238
  * @param {string} [options.dataplane] - Dataplane URL (overrides controller lookup)
239
+ * @param {string} [options.config] - Path to wizard.yaml config file (headless mode)
429
240
  * @returns {Promise<void>} Resolves when wizard completes
430
241
  * @throws {Error} If wizard fails
431
242
  */
432
243
  async function handleWizard(options = {}) {
433
- try {
434
- logger.log(chalk.blue('\n🧙 AI Fabrix External System Wizard\n'));
435
-
436
- // Get or prompt for app name
437
- let appName = options.app;
438
- if (!appName) {
439
- appName = await promptForAppName();
440
- }
441
-
442
- // Validate app name and check directory
443
- const shouldContinue = await validateAndCheckAppDirectory(appName);
444
- if (!shouldContinue) {
445
- return;
446
- }
447
-
448
- // Get dataplane URL and authentication
449
- const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(options, appName);
450
-
451
- // Execute wizard flow
452
- await executeWizardFlow(appName, dataplaneUrl, authConfig);
453
- } catch (error) {
454
- logger.error(chalk.red(`\n❌ Wizard failed: ${error.message}`));
455
- throw error;
244
+ // Check if headless mode (config file provided)
245
+ if (options.config) {
246
+ return await handleWizardHeadless(options);
456
247
  }
457
- }
458
-
459
- /**
460
- * Setup dataplane URL and authentication
461
- * @async
462
- * @function setupDataplaneAndAuth
463
- * @param {Object} options - Command options
464
- * @param {string} appName - Application name
465
- * @returns {Promise<Object>} Object with dataplaneUrl and authConfig
466
- * @throws {Error} If setup fails
467
- */
468
- async function setupDataplaneAndAuth(options, appName) {
469
- const configData = await config.getConfig();
470
- const environment = options.environment || 'dev';
471
- const controllerUrl = options.controller || configData.deployment?.controllerUrl || 'http://localhost:3000';
472
-
473
- // Get dataplane URL (either from option or from controller)
474
- let dataplaneUrl = options.dataplane;
475
- if (!dataplaneUrl) {
476
- // Get authentication first
477
- const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
478
- if (!authConfig.token && !authConfig.clientId) {
479
- throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
480
- }
481
248
 
482
- // Get dataplane URL from controller
483
- logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
484
- dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
485
- logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
249
+ logger.log(chalk.blue('\n\uD83E\uDDD9 AI Fabrix External System Wizard\n'));
486
250
 
487
- return { dataplaneUrl, authConfig };
251
+ // Get or prompt for app name
252
+ let appName = options.app;
253
+ if (!appName) {
254
+ appName = await promptForAppName();
488
255
  }
489
256
 
490
- // If dataplane URL provided directly, still need auth
491
- const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
492
- if (!authConfig.token && !authConfig.clientId) {
493
- throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
257
+ // Validate app name and check directory
258
+ const shouldContinue = await validateAndCheckAppDirectory(appName, true);
259
+ if (!shouldContinue) {
260
+ return;
494
261
  }
495
262
 
496
- return { dataplaneUrl, authConfig };
263
+ // Get dataplane URL and authentication
264
+ const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(options, appName);
265
+
266
+ // Execute wizard flow
267
+ await executeWizardFlow(appName, dataplaneUrl, authConfig);
497
268
  }
498
- module.exports = { handleWizard };
269
+
270
+ module.exports = { handleWizard, handleWizardHeadless };