@aifabrix/builder 2.32.1 → 2.32.3

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.
package/lib/api/index.js CHANGED
@@ -97,7 +97,8 @@ class ApiClient {
97
97
  }
98
98
 
99
99
  if (this.authConfig.type === 'bearer' || this.authConfig.type === 'client-token') {
100
- return await authenticatedApiCall(url, { method: 'GET', headers }, this.authConfig.token);
100
+ // Pass full authConfig to enable proper token refresh using controller URL
101
+ return await authenticatedApiCall(url, { method: 'GET', headers }, this.authConfig);
101
102
  }
102
103
 
103
104
  return await makeApiCall(url, { method: 'GET', headers });
@@ -141,7 +142,8 @@ class ApiClient {
141
142
  }
142
143
 
143
144
  if (this.authConfig.type === 'bearer' || this.authConfig.type === 'client-token') {
144
- return await authenticatedApiCall(url, requestOptions, this.authConfig.token);
145
+ // Pass full authConfig to enable proper token refresh using controller URL
146
+ return await authenticatedApiCall(url, requestOptions, this.authConfig);
145
147
  }
146
148
 
147
149
  return await makeApiCall(url, requestOptions);
@@ -170,7 +172,8 @@ class ApiClient {
170
172
  }
171
173
 
172
174
  if (this.authConfig.type === 'bearer' || this.authConfig.type === 'client-token') {
173
- return await authenticatedApiCall(url, requestOptions, this.authConfig.token);
175
+ // Pass full authConfig to enable proper token refresh using controller URL
176
+ return await authenticatedApiCall(url, requestOptions, this.authConfig);
174
177
  }
175
178
 
176
179
  return await makeApiCall(url, requestOptions);
@@ -199,7 +202,8 @@ class ApiClient {
199
202
  }
200
203
 
201
204
  if (this.authConfig.type === 'bearer' || this.authConfig.type === 'client-token') {
202
- return await authenticatedApiCall(url, requestOptions, this.authConfig.token);
205
+ // Pass full authConfig to enable proper token refresh using controller URL
206
+ return await authenticatedApiCall(url, requestOptions, this.authConfig);
203
207
  }
204
208
 
205
209
  return await makeApiCall(url, requestOptions);
@@ -223,7 +227,8 @@ class ApiClient {
223
227
  };
224
228
 
225
229
  if (this.authConfig.type === 'bearer' || this.authConfig.type === 'client-token') {
226
- return await authenticatedApiCall(url, requestOptions, this.authConfig.token);
230
+ // Pass full authConfig to enable proper token refresh using controller URL
231
+ return await authenticatedApiCall(url, requestOptions, this.authConfig);
227
232
  }
228
233
 
229
234
  return await makeApiCall(url, requestOptions);
@@ -8,42 +8,72 @@ const { ApiClient } = require('./index');
8
8
  const { uploadFile } = require('../utils/file-upload');
9
9
 
10
10
  /**
11
- * Select wizard mode
12
- * POST /api/v1/wizard/mode-selection
11
+ * Create wizard session
12
+ * POST /api/v1/wizard/sessions
13
13
  * @async
14
- * @function selectMode
14
+ * @function createWizardSession
15
15
  * @param {string} dataplaneUrl - Dataplane base URL
16
16
  * @param {Object} authConfig - Authentication configuration
17
17
  * @param {string} mode - Wizard mode ('create-system' | 'add-datasource')
18
- * @returns {Promise<Object>} Mode selection response
18
+ * @param {string} [systemId] - Existing system ID (required when mode='add-datasource')
19
+ * @returns {Promise<Object>} Session creation response with sessionId
19
20
  * @throws {Error} If request fails
20
21
  */
21
- async function selectMode(dataplaneUrl, authConfig, mode) {
22
+ async function createWizardSession(dataplaneUrl, authConfig, mode, systemId = null) {
22
23
  const client = new ApiClient(dataplaneUrl, authConfig);
23
- return await client.post('/api/v1/wizard/mode-selection', {
24
- body: { mode }
24
+ const body = { mode };
25
+ if (systemId) {
26
+ body.systemId = systemId;
27
+ }
28
+ return await client.post('/api/v1/wizard/sessions', {
29
+ body
25
30
  });
26
31
  }
27
32
 
28
33
  /**
29
- * Select wizard source
30
- * POST /api/v1/wizard/source-selection
34
+ * Get wizard session
35
+ * GET /api/v1/wizard/sessions/{sessionId}
31
36
  * @async
32
- * @function selectSource
37
+ * @function getWizardSession
33
38
  * @param {string} dataplaneUrl - Dataplane base URL
39
+ * @param {string} sessionId - Session ID
34
40
  * @param {Object} authConfig - Authentication configuration
35
- * @param {string} sourceType - Source type ('openapi-file' | 'openapi-url' | 'mcp-server' | 'known-platform')
36
- * @param {string} [sourceData] - Source data (file path, URL, etc.)
37
- * @returns {Promise<Object>} Source selection response
41
+ * @returns {Promise<Object>} Session state response
38
42
  * @throws {Error} If request fails
39
43
  */
40
- async function selectSource(dataplaneUrl, authConfig, sourceType, sourceData) {
44
+ async function getWizardSession(dataplaneUrl, sessionId, authConfig) {
41
45
  const client = new ApiClient(dataplaneUrl, authConfig);
42
- return await client.post('/api/v1/wizard/source-selection', {
43
- body: {
44
- sourceType,
45
- sourceData
46
- }
46
+ return await client.get(`/api/v1/wizard/sessions/${sessionId}`);
47
+ }
48
+
49
+ /**
50
+ * Update wizard session
51
+ * PATCH /api/v1/wizard/sessions/{sessionId}
52
+ * @async
53
+ * @function updateWizardSession
54
+ * @param {string} dataplaneUrl - Dataplane base URL
55
+ * @param {string} sessionId - Session ID
56
+ * @param {Object} authConfig - Authentication configuration
57
+ * @param {Object} updateData - Session update data
58
+ * @param {number} [updateData.currentStep] - Current wizard step (0-6)
59
+ * @param {string} [updateData.credentialIdOrKey] - Selected credential ID or key
60
+ * @param {Object} [updateData.openapiSpec] - Parsed OpenAPI specification
61
+ * @param {string} [updateData.mcpServerUrl] - MCP server URL
62
+ * @param {Array} [updateData.detectedTypes] - Detected API types
63
+ * @param {string} [updateData.selectedType] - Selected API type
64
+ * @param {string} [updateData.intent] - User intent
65
+ * @param {string} [updateData.fieldOnboardingLevel] - Field onboarding level
66
+ * @param {boolean} [updateData.enableOpenAPIGeneration] - Enable OpenAPI generation
67
+ * @param {Object} [updateData.systemConfig] - Generated system configuration
68
+ * @param {Object} [updateData.datasourceConfig] - Generated datasource configuration
69
+ * @param {Object} [updateData.validationResults] - Validation results
70
+ * @returns {Promise<Object>} Updated session state response
71
+ * @throws {Error} If request fails
72
+ */
73
+ async function updateWizardSession(dataplaneUrl, sessionId, authConfig, updateData) {
74
+ const client = new ApiClient(dataplaneUrl, authConfig);
75
+ return await client.patch(`/api/v1/wizard/sessions/${sessionId}`, {
76
+ body: updateData
47
77
  });
48
78
  }
49
79
 
@@ -165,8 +195,9 @@ async function getDeploymentDocs(dataplaneUrl, authConfig, systemKey) {
165
195
  }
166
196
 
167
197
  module.exports = {
168
- selectMode,
169
- selectSource,
198
+ createWizardSession,
199
+ getWizardSession,
200
+ updateWizardSession,
170
201
  parseOpenApi,
171
202
  detectType,
172
203
  generateConfig,
package/lib/app/list.js CHANGED
@@ -126,14 +126,35 @@ async function findDeviceTokenFromConfig(deviceConfig) {
126
126
  return null;
127
127
  }
128
128
 
129
+ /**
130
+ * Format URL and port for display
131
+ * @param {Object} app - Application object
132
+ * @returns {string} Formatted URL and port string
133
+ */
134
+ function formatUrlAndPort(app) {
135
+ const url = app.url || app.dataplaneUrl || app.dataplane?.url || app.configuration?.dataplaneUrl || null;
136
+ const port = app.port || app.configuration?.port || null;
137
+
138
+ const parts = [];
139
+ if (url) {
140
+ parts.push(`URL: ${chalk.blue(url)}`);
141
+ }
142
+ if (port) {
143
+ parts.push(`Port: ${chalk.blue(port)}`);
144
+ }
145
+
146
+ return parts.length > 0 ? ` (${parts.join(', ')})` : '';
147
+ }
148
+
129
149
  /**
130
150
  * Display applications list
131
151
  * @param {Array} applications - Array of application objects
132
152
  * @param {string} environment - Environment name or key
153
+ * @param {string} controllerUrl - Controller URL
133
154
  */
134
- function displayApplications(applications, environment) {
155
+ function displayApplications(applications, environment, controllerUrl) {
135
156
  const environmentName = environment || 'miso';
136
- const header = `Applications in ${environmentName} environment`;
157
+ const header = `Applications in ${environmentName} environment (${controllerUrl})`;
137
158
 
138
159
  if (applications.length === 0) {
139
160
  logger.log(chalk.bold(`\n📱 ${header}:\n`));
@@ -144,7 +165,8 @@ function displayApplications(applications, environment) {
144
165
  logger.log(chalk.bold(`\n📱 ${header}:\n`));
145
166
  applications.forEach((app) => {
146
167
  const hasPipeline = app.configuration?.pipeline?.isActive ? '✓' : '✗';
147
- logger.log(`${hasPipeline} ${chalk.cyan(app.key)} - ${app.displayName} (${app.status || 'unknown'})`);
168
+ const urlAndPort = formatUrlAndPort(app);
169
+ logger.log(`${hasPipeline} ${chalk.cyan(app.key)} - ${app.displayName} (${app.status || 'unknown'})${urlAndPort}`);
148
170
  });
149
171
  logger.log('');
150
172
  }
@@ -152,23 +174,19 @@ function displayApplications(applications, environment) {
152
174
  /**
153
175
  * Try to get device token from controller URL
154
176
  * @async
155
- * @param {string} controllerUrl - Controller URL
156
- * @returns {Promise<Object|null>} Object with token and controllerUrl, or null
177
+ * @param {string} controllerUrl - Controller URL (explicitly provided by user)
178
+ * @returns {Promise<Object|null>} Object with token and controllerUrl, or null if token not found
179
+ * @throws {Error} If authentication/refresh fails
157
180
  */
158
181
  async function tryGetTokenFromController(controllerUrl) {
159
- try {
160
- const normalizedUrl = normalizeControllerUrl(controllerUrl);
161
- const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
162
- if (deviceToken && deviceToken.token) {
163
- return {
164
- token: deviceToken.token,
165
- actualControllerUrl: deviceToken.controller || normalizedUrl
166
- };
167
- }
168
- } catch (error) {
169
- logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
170
- logger.error(chalk.gray(`Error: ${error.message}`));
171
- process.exit(1);
182
+ const normalizedUrl = normalizeControllerUrl(controllerUrl);
183
+ const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
184
+ if (deviceToken && deviceToken.token) {
185
+ // Always use the provided controller URL (normalized) to ensure we use the exact URL the user specified
186
+ return {
187
+ token: deviceToken.token,
188
+ actualControllerUrl: normalizedUrl
189
+ };
172
190
  }
173
191
  return null;
174
192
  }
@@ -215,25 +233,36 @@ function validateAuthToken(token, actualControllerUrl, controllerUrl) {
215
233
  /**
216
234
  * Get authentication token for listing applications
217
235
  * @async
218
- * @param {string} [controllerUrl] - Optional controller URL
236
+ * @param {string} [controllerUrl] - Optional controller URL (if provided, must use this specific URL)
219
237
  * @param {Object} config - Configuration object
220
238
  * @returns {Promise<Object>} Object with token and actualControllerUrl
221
239
  * @throws {Error} If authentication fails
222
240
  */
223
241
  async function getListAuthToken(controllerUrl, config) {
224
- // Try to get token from controller URL first
225
- let authResult = null;
242
+ // If controller URL is explicitly provided, use only that URL (no fallback)
226
243
  if (controllerUrl) {
227
- authResult = await tryGetTokenFromController(controllerUrl);
244
+ const authResult = await tryGetTokenFromController(controllerUrl);
245
+ if (!authResult || !authResult.token) {
246
+ // No token found for explicitly provided controller URL
247
+ logger.error(chalk.red(`❌ No authentication token found for controller: ${controllerUrl}`));
248
+ logger.error(chalk.gray('Please login to this controller using: aifabrix login'));
249
+ process.exit(1);
250
+ // Return to prevent further execution in tests where process.exit is mocked
251
+ return { token: null, actualControllerUrl: null };
252
+ }
253
+ return validateAuthToken(authResult.token, authResult.actualControllerUrl, controllerUrl);
228
254
  }
229
255
 
230
- // If no token yet, try to find device token from config
231
- if (!authResult && config.device) {
232
- authResult = await tryGetTokenFromConfig(config.device);
256
+ // If no controller URL provided, try to find device token from config
257
+ if (config.device) {
258
+ const authResult = await tryGetTokenFromConfig(config.device);
259
+ if (authResult && authResult.token) {
260
+ return validateAuthToken(authResult.token, authResult.actualControllerUrl, null);
261
+ }
233
262
  }
234
263
 
235
- // Validate and return token or exit
236
- return validateAuthToken(authResult?.token || null, authResult?.actualControllerUrl || null, controllerUrl);
264
+ // No token found anywhere
265
+ return validateAuthToken(null, null, null);
237
266
  }
238
267
 
239
268
  /**
@@ -271,12 +300,17 @@ async function listApplications(options) {
271
300
  const controllerUrl = options.controller || null;
272
301
  const { token, actualControllerUrl } = await getListAuthToken(controllerUrl, config);
273
302
 
303
+ // Check if authentication succeeded (may be null after process.exit in tests)
304
+ if (!token || !actualControllerUrl) {
305
+ return;
306
+ }
307
+
274
308
  // Use centralized API client
275
309
  const authConfig = { type: 'bearer', token: token };
276
310
  try {
277
311
  const response = await listEnvironmentApplications(actualControllerUrl, options.environment, authConfig);
278
312
  const applications = handleListResponse(response, actualControllerUrl);
279
- displayApplications(applications, options.environment);
313
+ displayApplications(applications, options.environment, actualControllerUrl);
280
314
  } catch (error) {
281
315
  logger.error(chalk.red(`❌ Failed to list applications from controller: ${actualControllerUrl}`));
282
316
  logger.error(chalk.gray(`Error: ${error.message}`));
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  const inquirer = require('inquirer');
12
+ const { getDefaultControllerUrl } = require('../utils/controller-url');
12
13
 
13
14
  /**
14
15
  * Builds basic questions (port, language)
@@ -223,10 +224,11 @@ function buildExternalSystemQuestions(options, appName) {
223
224
 
224
225
  /**
225
226
  * Builds workflow questions (GitHub, Controller)
227
+ * @async
226
228
  * @param {Object} options - Provided options
227
- * @returns {Array} Array of question objects
229
+ * @returns {Promise<Array>} Array of question objects
228
230
  */
229
- function buildWorkflowQuestions(options) {
231
+ async function buildWorkflowQuestions(options) {
230
232
  const questions = [];
231
233
 
232
234
  // GitHub workflows
@@ -255,11 +257,13 @@ function buildWorkflowQuestions(options) {
255
257
  if (!options.controllerUrl && options.controller &&
256
258
  !Object.prototype.hasOwnProperty.call(options, 'controllerUrl')) {
257
259
  const misoHost = process.env.MISO_HOST || 'localhost';
260
+ const defaultControllerUrl = await getDefaultControllerUrl();
261
+ const defaultUrl = defaultControllerUrl.replace('http://localhost:', `http://${misoHost}:`);
258
262
  questions.push({
259
263
  type: 'input',
260
264
  name: 'controllerUrl',
261
265
  message: 'Enter Controller URL:',
262
- default: `http://${misoHost}:3000`,
266
+ default: defaultUrl,
263
267
  when: (answers) => answers.controller === true
264
268
  });
265
269
  }
@@ -424,14 +428,14 @@ async function promptForOptions(appName, options) {
424
428
  // For external type, prompt for external system configuration
425
429
  questions = [
426
430
  ...buildExternalSystemQuestions(options, appName),
427
- ...buildWorkflowQuestions(options)
431
+ ...(await buildWorkflowQuestions(options))
428
432
  ];
429
433
  } else {
430
434
  // For regular apps, use standard prompts
431
435
  questions = [
432
436
  ...buildBasicQuestions(options, appType),
433
437
  ...buildServiceQuestions(options, appType),
434
- ...buildWorkflowQuestions(options)
438
+ ...(await buildWorkflowQuestions(options))
435
439
  ];
436
440
  }
437
441
 
@@ -152,7 +152,8 @@ function displayRotationResults(appKey, environment, credentials, apiUrl, messag
152
152
  logger.log(chalk.green('✅ Secret rotated successfully!\n'));
153
153
  logger.log(chalk.bold('📋 Application Details:'));
154
154
  logger.log(` Key: ${appKey}`);
155
- logger.log(` Environment: ${environment}\n`);
155
+ logger.log(` Environment: ${environment}`);
156
+ logger.log(` Controller: ${apiUrl}\n`);
156
157
 
157
158
  logger.log(chalk.bold.yellow('🔑 NEW CREDENTIALS:'));
158
159
  logger.log(chalk.yellow(` Client ID: ${credentials.clientId}`));
package/lib/cli.js CHANGED
@@ -22,6 +22,7 @@ const logger = require('./utils/logger');
22
22
  const { validateCommand, handleCommandError } = require('./utils/cli-utils');
23
23
  const { handleLogin } = require('./commands/login');
24
24
  const { handleLogout } = require('./commands/logout');
25
+ const { handleAuthStatus } = require('./commands/auth-status');
25
26
  const { handleSecure } = require('./commands/secure');
26
27
  const { handleSecretsSet } = require('./commands/secrets-set');
27
28
 
@@ -32,14 +33,14 @@ const { handleSecretsSet } = require('./commands/secrets-set');
32
33
  function setupAuthCommands(program) {
33
34
  program.command('login')
34
35
  .description('Authenticate with Miso Controller')
35
- .option('-c, --controller <url>', 'Controller URL', 'http://localhost:3000')
36
- .option('-m, --method <method>', 'Authentication method (device|credentials)')
36
+ .option('-c, --controller <url>', 'Controller URL (default: calculated based on developer ID: http://localhost:${3000 + (developerId * 100)})')
37
+ .option('-m, --method <method>', 'Authentication method (device|credentials)', 'device')
37
38
  .option('-a, --app <app>', 'Application name (required for credentials method, reads from secrets.local.yaml)')
38
39
  .option('--client-id <id>', 'Client ID (for credentials method, overrides secrets.local.yaml)')
39
40
  .option('--client-secret <secret>', 'Client Secret (for credentials method, overrides secrets.local.yaml)')
40
41
  .option('-e, --environment <env>', 'Environment key (updates root-level environment in config.yaml, e.g., miso, dev, tst, pro)')
41
- .option('--offline', 'Request offline token (adds offline_access scope, device flow only)')
42
- .option('--scope <scopes>', 'Custom OAuth2 scope string (device flow only, default: "openid profile email")')
42
+ .option('--online', 'Request online-only token (excludes offline_access scope, device flow only)')
43
+ .option('--scope <scopes>', 'Custom OAuth2 scope string (device flow only, default: "openid profile email offline_access")')
43
44
  .action(async(options) => {
44
45
  try {
45
46
  await handleLogin(options);
@@ -62,6 +63,34 @@ function setupAuthCommands(program) {
62
63
  process.exit(1);
63
64
  }
64
65
  });
66
+
67
+ const authStatusHandler = async(options) => {
68
+ try {
69
+ await handleAuthStatus(options);
70
+ } catch (error) {
71
+ handleCommandError(error, 'auth status');
72
+ process.exit(1);
73
+ }
74
+ };
75
+
76
+ // Use nested command pattern for multi-word commands (like environment deploy)
77
+ const auth = program
78
+ .command('auth')
79
+ .description('Authentication commands');
80
+
81
+ auth
82
+ .command('status')
83
+ .description('Display authentication status for current controller and environment')
84
+ .option('-c, --controller <url>', 'Check status for specific controller (uses developer ID-based default if not provided)')
85
+ .option('-e, --environment <env>', 'Check status for specific environment')
86
+ .action(authStatusHandler);
87
+
88
+ // Alias: status (register as separate command since Commander.js doesn't support multi-word aliases)
89
+ program.command('status')
90
+ .description('Display authentication status (alias for auth status)')
91
+ .option('-c, --controller <url>', 'Check status for specific controller (uses developer ID-based default if not provided)')
92
+ .option('-e, --environment <env>', 'Check status for specific environment')
93
+ .action(authStatusHandler);
65
94
  }
66
95
 
67
96
  /**