@aifabrix/builder 2.10.0 → 2.11.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.
@@ -48,13 +48,13 @@ aifabrix validate hubspot
48
48
 
49
49
  ```bash
50
50
  # Login to controller
51
- aifabrix login --controller https://controller.aifabrix.ai --method device --environment dev
51
+ aifabrix login --controller http://localhost:3100 --method device --environment dev
52
52
 
53
53
  # Register application
54
54
  aifabrix app register hubspot --environment dev
55
55
 
56
56
  # Deploy entire system
57
- aifabrix deploy hubspot --controller https://controller.aifabrix.ai --environment dev
57
+ aifabrix deploy hubspot --controller http://localhost:3100 --environment dev
58
58
 
59
59
  # Or deploy individual datasources for testing
60
60
  aifabrix datasource deploy hubspot-company --environment dev --file integration/hubspot/hubspot-deploy-company.json
@@ -58,7 +58,11 @@
58
58
  "field": "select",
59
59
  "label": "HubSpot API Version",
60
60
  "placeholder": "Select API version",
61
- "options": ["v1", "v2", "v3"],
61
+ "options": [
62
+ "v1",
63
+ "v2",
64
+ "v3"
65
+ ],
62
66
  "validation": {
63
67
  "required": false
64
68
  }
@@ -86,6 +90,10 @@
86
90
  "documentKey": "hubspot-v3",
87
91
  "autoDiscoverEntities": false
88
92
  },
89
- "tags": ["crm", "sales", "marketing", "hubspot"]
90
- }
91
-
93
+ "tags": [
94
+ "crm",
95
+ "sales",
96
+ "marketing",
97
+ "hubspot"
98
+ ]
99
+ }
package/lib/app-config.js CHANGED
@@ -46,23 +46,54 @@ async function generateVariablesYamlFile(appPath, appName, config) {
46
46
  }
47
47
  }
48
48
 
49
+ /**
50
+ * Generates env.template content for external systems based on authentication type
51
+ * @param {Object} config - Application configuration with authType and systemKey
52
+ * @param {string} appName - Application name (used as fallback for systemKey)
53
+ * @returns {string} Environment template content
54
+ */
55
+ function generateExternalSystemEnvTemplate(config, appName) {
56
+ const systemKey = config.systemKey || appName;
57
+ const authType = config.authType || 'apikey';
58
+ const lines = [
59
+ `# ${systemKey} ${authType.toUpperCase()} Configuration`,
60
+ '# These values are set via the Miso Controller interface or Dataplane portal',
61
+ '# Values are stored in Key Vault automatically by the platform',
62
+ ''
63
+ ];
64
+
65
+ if (authType === 'oauth2') {
66
+ lines.push('CLIENTID=kv://' + systemKey + '-clientidKeyVault');
67
+ lines.push('CLIENTSECRET=kv://' + systemKey + '-clientsecretKeyVault');
68
+ lines.push('TOKENURL=https://api.example.com/oauth/token');
69
+ lines.push('REDIRECT_URI=kv://' + systemKey + '-redirect-uriKeyVault');
70
+ } else if (authType === 'apikey') {
71
+ lines.push('API_KEY=kv://' + systemKey + '-api-keyKeyVault');
72
+ } else if (authType === 'basic') {
73
+ lines.push('USERNAME=kv://' + systemKey + '-usernameKeyVault');
74
+ lines.push('PASSWORD=kv://' + systemKey + '-passwordKeyVault');
75
+ }
76
+
77
+ return lines.join('\n');
78
+ }
79
+
49
80
  /**
50
81
  * Generates env.template file if it doesn't exist
51
82
  * @async
52
83
  * @param {string} appPath - Path to application directory
84
+ * @param {string} appName - Application name
53
85
  * @param {Object} config - Application configuration
54
86
  * @param {Object} existingEnv - Existing environment variables
55
87
  */
56
- async function generateEnvTemplateFile(appPath, config, existingEnv) {
57
- // Skip env.template for external type
58
- if (config.type === 'external') {
59
- return;
60
- }
61
-
88
+ async function generateEnvTemplateFile(appPath, appName, config, existingEnv) {
62
89
  const envTemplatePath = path.join(appPath, 'env.template');
63
90
  if (!(await fileExists(envTemplatePath))) {
64
91
  let envTemplate;
65
- if (existingEnv) {
92
+
93
+ if (config.type === 'external') {
94
+ // Generate env.template for external systems based on authType
95
+ envTemplate = generateExternalSystemEnvTemplate(config, appName);
96
+ } else if (existingEnv) {
66
97
  const envResult = await generateEnvTemplateFromReader(config, existingEnv);
67
98
  envTemplate = envResult.template;
68
99
 
@@ -155,7 +186,7 @@ async function generateDeployJsonFile(appPath, appName, config) {
155
186
  async function generateConfigFiles(appPath, appName, config, existingEnv) {
156
187
  try {
157
188
  await generateVariablesYamlFile(appPath, appName, config);
158
- await generateEnvTemplateFile(appPath, config, existingEnv);
189
+ await generateEnvTemplateFile(appPath, appName, config, existingEnv);
159
190
  await generateRbacYamlFile(appPath, appName, config);
160
191
  await generateDeployJsonFile(appPath, appName, config);
161
192
  await generateReadmeMdFile(appPath, appName, config);
@@ -8,361 +8,98 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
- const fs = require('fs').promises;
12
- const path = require('path');
13
11
  const chalk = require('chalk');
14
- const yaml = require('js-yaml');
15
- const { getConfig } = require('./config');
16
- const { authenticatedApiCall } = require('./utils/api');
17
- const { formatApiError } = require('./utils/api-error-handler');
18
12
  const logger = require('./utils/logger');
19
13
  const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
20
14
  const { updateEnvTemplate } = require('./utils/env-template');
21
- const { getOrRefreshDeviceToken } = require('./utils/token-manager');
22
- const { detectAppType } = require('./utils/paths');
23
15
  const { generateEnvFile } = require('./secrets');
24
-
25
- // Import createApp to auto-generate config if missing
26
- let createApp;
27
- try {
28
- createApp = require('./app').createApp;
29
- } catch {
30
- createApp = null;
31
- }
32
-
33
- /**
34
- * Validation schema for application registration
35
- */
36
- const registerApplicationSchema = {
37
- environmentId: (val) => {
38
- if (!val || val.length < 1) {
39
- throw new Error('Invalid environment ID format');
40
- }
41
- return val;
42
- },
43
- key: (val) => {
44
- if (!val || val.length < 1) {
45
- throw new Error('Application key is required');
46
- }
47
- if (val.length > 50) {
48
- throw new Error('Application key must be at most 50 characters');
49
- }
50
- if (!/^[a-z0-9-]+$/.test(val)) {
51
- throw new Error('Application key must contain only lowercase letters, numbers, and hyphens');
52
- }
53
- return val;
54
- },
55
- displayName: (val) => {
56
- if (!val || val.length < 1) {
57
- throw new Error('Display name is required');
58
- }
59
- if (val.length > 100) {
60
- throw new Error('Display name must be at most 100 characters');
61
- }
62
- return val;
63
- },
64
- description: (val) => val || undefined,
65
- configuration: (val) => {
66
- const validTypes = ['webapp', 'api', 'service', 'functionapp'];
67
- const validRegistryModes = ['acr', 'external', 'public'];
68
-
69
- if (!val || !val.type || !validTypes.includes(val.type)) {
70
- throw new Error('Configuration type must be one of: webapp, api, service, functionapp');
71
- }
72
- if (!val.registryMode || !validRegistryModes.includes(val.registryMode)) {
73
- throw new Error('Registry mode must be one of: acr, external, public');
74
- }
75
- if (val.port !== undefined) {
76
- if (!Number.isInteger(val.port) || val.port < 1 || val.port > 65535) {
77
- throw new Error('Port must be an integer between 1 and 65535');
78
- }
79
- }
80
- return val;
81
- }
82
- };
16
+ const { registerApplicationSchema, validateAppRegistrationData } = require('./utils/app-register-validator');
17
+ const {
18
+ loadVariablesYaml,
19
+ createMinimalAppIfNeeded,
20
+ extractAppConfiguration
21
+ } = require('./utils/app-register-config');
22
+ const { checkAuthentication } = require('./utils/app-register-auth');
23
+ const { callRegisterApi } = require('./utils/app-register-api');
24
+ const { displayRegistrationResults, getEnvironmentPrefix } = require('./utils/app-register-display');
83
25
 
84
26
  /**
85
- * Load variables.yaml file for an application
86
- * @async
87
- * @param {string} appKey - Application key
88
- * @returns {Promise<{variables: Object, created: boolean}>} Variables and creation flag
89
- */
90
- async function loadVariablesYaml(appKey) {
91
- // Detect app type and get correct path (integration or builder)
92
- const { appPath } = await detectAppType(appKey);
93
- const variablesPath = path.join(appPath, 'variables.yaml');
94
-
95
- try {
96
- const variablesContent = await fs.readFile(variablesPath, 'utf-8');
97
- return { variables: yaml.load(variablesContent), created: false };
98
- } catch (error) {
99
- if (error.code === 'ENOENT') {
100
- logger.log(chalk.yellow(`āš ļø variables.yaml not found for ${appKey}`));
101
- logger.log(chalk.yellow('šŸ“ Creating minimal configuration...\n'));
102
- return { variables: null, created: true };
103
- }
104
- throw new Error(`Failed to read variables.yaml: ${error.message}`);
105
- }
106
- }
107
-
108
- /**
109
- * Create minimal application configuration if needed
110
- * @async
111
- * @param {string} appKey - Application key
27
+ * Build registration data payload from app configuration
28
+ * @param {Object} appConfig - Application configuration
112
29
  * @param {Object} options - Registration options
113
- * @returns {Promise<Object>} Variables after creation
30
+ * @returns {Object} Registration data payload
114
31
  */
115
- async function createMinimalAppIfNeeded(appKey, options) {
116
- if (!createApp) {
117
- throw new Error('Cannot auto-create application: createApp function not available');
118
- }
119
-
120
- await createApp(appKey, {
121
- port: options.port,
122
- language: 'typescript',
123
- database: false,
124
- redis: false,
125
- storage: false,
126
- authentication: false
127
- });
128
-
129
- // Detect app type and get correct path (integration or builder)
130
- const { appPath } = await detectAppType(appKey);
131
- const variablesPath = path.join(appPath, 'variables.yaml');
132
- const variablesContent = await fs.readFile(variablesPath, 'utf-8');
133
- return yaml.load(variablesContent);
134
- }
135
-
136
- /**
137
- * Extract application configuration from variables.yaml
138
- * @param {Object} variables - Variables from YAML file
139
- * @param {string} appKey - Application key
140
- * @param {Object} options - Registration options
141
- * @returns {Object} Extracted configuration
142
- */
143
- function extractAppConfiguration(variables, appKey, options) {
144
- const appKeyFromFile = variables.app?.key || appKey;
145
- const displayName = variables.app?.name || options.name || appKey;
146
- const description = variables.app?.description || '';
147
-
148
- // Handle external type
149
- if (variables.app?.type === 'external') {
150
- return {
151
- appKey: appKeyFromFile,
152
- displayName,
153
- description,
154
- appType: 'external',
155
- registryMode: 'external',
156
- port: null, // External systems don't need ports
157
- language: null // External systems don't need language
158
- };
159
- }
160
-
161
- const appType = variables.build?.language === 'typescript' ? 'webapp' : 'service';
162
- const registryMode = 'external';
163
- const port = variables.build?.port || options.port || 3000;
164
- const language = variables.build?.language || 'typescript';
165
-
166
- return {
167
- appKey: appKeyFromFile,
168
- displayName,
169
- description,
170
- appType,
171
- registryMode,
172
- port,
173
- language
32
+ function buildRegistrationData(appConfig, options) {
33
+ const registrationData = {
34
+ key: appConfig.appKey,
35
+ displayName: appConfig.displayName,
36
+ type: appConfig.appType
174
37
  };
175
- }
176
-
177
- /**
178
- * Validate application registration data
179
- * @async
180
- * @param {Object} config - Application configuration
181
- * @param {string} originalAppKey - Original app key for error messages
182
- * @throws {Error} If validation fails
183
- */
184
- async function validateAppRegistrationData(config, originalAppKey) {
185
- const missingFields = [];
186
- if (!config.appKey) missingFields.push('app.key');
187
- if (!config.displayName) missingFields.push('app.name');
188
-
189
- if (missingFields.length > 0) {
190
- logger.error(chalk.red('āŒ Missing required fields in variables.yaml:'));
191
- missingFields.forEach(field => logger.error(chalk.red(` - ${field}`)));
192
- // Detect app type to show correct path
193
- const { appPath } = await detectAppType(originalAppKey);
194
- const relativePath = path.relative(process.cwd(), appPath);
195
- logger.error(chalk.red(`\n Please update ${relativePath}/variables.yaml and try again.`));
196
- process.exit(1);
197
- }
198
38
 
199
- try {
200
- registerApplicationSchema.key(config.appKey);
201
- registerApplicationSchema.displayName(config.displayName);
202
- registerApplicationSchema.configuration({
203
- type: config.appType,
204
- registryMode: config.registryMode,
205
- port: config.port
206
- });
207
- } catch (error) {
208
- logger.error(chalk.red(`āŒ Invalid configuration: ${error.message}`));
209
- process.exit(1);
39
+ // Add optional fields only if they have values
40
+ if (appConfig.description || options.description) {
41
+ registrationData.description = appConfig.description || options.description;
210
42
  }
211
- }
212
-
213
- /**
214
- * Check if user is authenticated and get token
215
- * @async
216
- * @param {string} [controllerUrl] - Optional controller URL from variables.yaml
217
- * @param {string} [environment] - Optional environment key
218
- * @returns {Promise<{apiUrl: string, token: string}>} Configuration with API URL and token
219
- */
220
- async function checkAuthentication(controllerUrl, environment) {
221
- const config = await getConfig();
222
-
223
- // Try to get controller URL from parameter, config, or device tokens
224
- let finalControllerUrl = controllerUrl;
225
- let token = null;
226
43
 
227
- // If controller URL provided, try to get device token
228
- if (finalControllerUrl) {
229
- const deviceToken = await getOrRefreshDeviceToken(finalControllerUrl);
230
- if (deviceToken && deviceToken.token) {
231
- token = deviceToken.token;
232
- finalControllerUrl = deviceToken.controller;
44
+ // Handle external type vs non-external types differently
45
+ if (appConfig.appType === 'external') {
46
+ // For external type: include externalIntegration, exclude registryMode/port/image
47
+ if (appConfig.externalIntegration) {
48
+ registrationData.externalIntegration = appConfig.externalIntegration;
233
49
  }
234
- }
50
+ } else {
51
+ // For non-external types: include registryMode, port, image
52
+ registrationData.registryMode = appConfig.registryMode;
235
53
 
236
- // If no token yet, try to find any device token in config
237
- if (!token && config.device) {
238
- const deviceUrls = Object.keys(config.device);
239
- if (deviceUrls.length > 0) {
240
- // Use first available device token
241
- finalControllerUrl = deviceUrls[0];
242
- const deviceToken = await getOrRefreshDeviceToken(finalControllerUrl);
243
- if (deviceToken && deviceToken.token) {
244
- token = deviceToken.token;
245
- finalControllerUrl = deviceToken.controller;
246
- }
54
+ // Port is required for non-external types
55
+ if (appConfig.port) {
56
+ registrationData.port = appConfig.port;
247
57
  }
248
- }
249
-
250
- // If still no token, check for client token (requires environment and app)
251
- if (!token && environment) {
252
- // For app register, we don't have an app yet, so client tokens won't work
253
- // This is expected - device tokens should be used for registration
254
- }
255
58
 
256
- if (!token || !finalControllerUrl) {
257
- logger.error(chalk.red('āŒ Not logged in. Run: aifabrix login'));
258
- logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
259
- process.exit(1);
59
+ // Image is required for non-external types
60
+ if (appConfig.image) {
61
+ registrationData.image = appConfig.image;
62
+ }
260
63
  }
261
64
 
262
- return {
263
- apiUrl: finalControllerUrl,
264
- token: token
265
- };
65
+ return registrationData;
266
66
  }
267
67
 
268
68
  /**
269
- * Call registration API
69
+ * Save credentials to local secrets if localhost
270
70
  * @async
71
+ * @param {Object} responseData - Registration response data
271
72
  * @param {string} apiUrl - API URL
272
- * @param {string} token - Authentication token
273
- * @param {string} environment - Environment ID
274
- * @param {Object} registrationData - Registration data
275
- * @returns {Promise<Object>} API response
276
73
  */
277
- async function callRegisterApi(apiUrl, token, environment, registrationData) {
278
- const response = await authenticatedApiCall(
279
- `${apiUrl}/api/v1/environments/${encodeURIComponent(environment)}/applications/register`,
280
- {
281
- method: 'POST',
282
- body: JSON.stringify(registrationData)
283
- },
284
- token
285
- );
286
-
287
- if (!response.success) {
288
- const formattedError = response.formattedError || formatApiError(response);
289
- logger.error(formattedError);
290
- process.exit(1);
74
+ async function saveLocalCredentials(responseData, apiUrl) {
75
+ if (!isLocalhost(apiUrl)) {
76
+ return;
291
77
  }
292
78
 
293
- // Handle API response structure:
294
- // makeApiCall returns: { success: true, data: <API response> }
295
- // API response can be:
296
- // 1. Direct format: { application: {...}, credentials: {...} }
297
- // 2. Wrapped format: { success: true, data: { application: {...}, credentials: {...} } }
298
- const apiResponse = response.data;
299
- if (apiResponse && apiResponse.data && apiResponse.data.application) {
300
- // Wrapped format: use apiResponse.data
301
- return apiResponse.data;
302
- } else if (apiResponse && apiResponse.application) {
303
- // Direct format: use apiResponse directly
304
- return apiResponse;
305
- }
306
- // Fallback: return apiResponse as-is (shouldn't happen, but handle gracefully)
307
- logger.error(chalk.red('āŒ Invalid response: missing application data'));
308
- logger.error(chalk.gray('\nFull response for debugging:'));
309
- logger.error(chalk.gray(JSON.stringify(response, null, 2)));
310
- process.exit(1);
311
-
312
- }
313
-
314
- /**
315
- * Get environment prefix for GitHub Secrets
316
- * @param {string} environment - Environment key (e.g., 'dev', 'tst', 'pro', 'miso')
317
- * @returns {string} Uppercase prefix (e.g., 'DEV', 'TST', 'PRO', 'MISO')
318
- */
319
- function getEnvironmentPrefix(environment) {
320
- if (!environment) {
321
- return 'DEV';
322
- }
323
- // Convert to uppercase and handle common variations
324
- const env = environment.toLowerCase();
325
- if (env === 'dev' || env === 'development') {
326
- return 'DEV';
327
- }
328
- if (env === 'tst' || env === 'test' || env === 'staging') {
329
- return 'TST';
330
- }
331
- if (env === 'pro' || env === 'prod' || env === 'production') {
332
- return 'PRO';
333
- }
334
- // For other environments (e.g., 'miso'), uppercase the entire string
335
- // Use full string if 4 characters or less, otherwise use first 4 characters
336
- const upper = environment.toUpperCase();
337
- return upper.length <= 4 ? upper : upper.substring(0, 4);
338
- }
79
+ const registeredAppKey = responseData.application.key;
80
+ const clientIdKey = `${registeredAppKey}-client-idKeyVault`;
81
+ const clientSecretKey = `${registeredAppKey}-client-secretKeyVault`;
339
82
 
340
- /**
341
- * Display registration success and credentials
342
- * @param {Object} data - Registration response data
343
- * @param {string} apiUrl - API URL
344
- * @param {string} environment - Environment key
345
- */
346
- function displayRegistrationResults(data, apiUrl, environment) {
347
- logger.log(chalk.green('āœ… Application registered successfully!\n'));
348
- logger.log(chalk.bold('šŸ“‹ Application Details:'));
349
- logger.log(` ID: ${data.application.id}`);
350
- logger.log(` Key: ${data.application.key}`);
351
- logger.log(` Display Name: ${data.application.displayName}\n`);
83
+ try {
84
+ await saveLocalSecret(clientIdKey, responseData.credentials.clientId);
85
+ await saveLocalSecret(clientSecretKey, responseData.credentials.clientSecret);
352
86
 
353
- logger.log(chalk.bold.yellow('šŸ”‘ CREDENTIALS (save these immediately):'));
354
- logger.log(chalk.yellow(` Client ID: ${data.credentials.clientId}`));
355
- logger.log(chalk.yellow(` Client Secret: ${data.credentials.clientSecret}\n`));
87
+ // Update env.template
88
+ await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, apiUrl);
356
89
 
357
- logger.log(chalk.red('āš ļø IMPORTANT: Client Secret will not be shown again!\n'));
90
+ // Regenerate .env file with updated credentials
91
+ try {
92
+ await generateEnvFile(registeredAppKey, null, 'local');
93
+ logger.log(chalk.green('āœ“ .env file updated with new credentials'));
94
+ } catch (error) {
95
+ logger.warn(chalk.yellow(`āš ļø Could not regenerate .env file: ${error.message}`));
96
+ }
358
97
 
359
- const envPrefix = getEnvironmentPrefix(environment);
360
- logger.log(chalk.bold('šŸ“ Add to GitHub Secrets:'));
361
- logger.log(chalk.cyan(' Repository level:'));
362
- logger.log(chalk.cyan(` MISO_CONTROLLER_URL = ${apiUrl}`));
363
- logger.log(chalk.cyan(`\n Environment level (${environment}):`));
364
- logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTID = ${data.credentials.clientId}`));
365
- logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTSECRET = ${data.credentials.clientSecret}\n`));
98
+ logger.log(chalk.green('\nāœ“ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
99
+ logger.log(chalk.green('āœ“ env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
100
+ } catch (error) {
101
+ logger.warn(chalk.yellow(`āš ļø Could not save credentials locally: ${error.message}`));
102
+ }
366
103
  }
367
104
 
368
105
  /**
@@ -381,49 +118,21 @@ async function registerApplication(appKey, options) {
381
118
 
382
119
  // Load variables.yaml
383
120
  const { variables, created } = await loadVariablesYaml(appKey);
384
- let finalVariables = variables;
385
-
386
- // Create minimal app if needed
387
- if (created) {
388
- finalVariables = await createMinimalAppIfNeeded(appKey, options);
389
- }
121
+ const finalVariables = created
122
+ ? await createMinimalAppIfNeeded(appKey, options)
123
+ : variables;
390
124
 
391
- // Extract configuration
392
- const appConfig = extractAppConfiguration(finalVariables, appKey, options);
393
-
394
- // Validate configuration (pass original appKey for error messages)
125
+ // Extract and validate configuration
126
+ const appConfig = await extractAppConfiguration(finalVariables, appKey, options);
395
127
  await validateAppRegistrationData(appConfig, appKey);
396
128
 
397
- // Get controller URL from variables.yaml if available
129
+ // Authenticate and get API configuration
398
130
  const controllerUrl = finalVariables?.deployment?.controllerUrl;
399
-
400
- // Check authentication (try device token first, supports registration flow)
401
131
  const authConfig = await checkAuthentication(controllerUrl, options.environment);
402
-
403
- // Validate environment
404
132
  const environment = registerApplicationSchema.environmentId(options.environment);
405
133
 
406
- // Prepare registration data to match OpenAPI RegisterApplicationRequest schema
407
- // Schema: { key, displayName, description?, configuration: { type, registryMode, port?, image? } }
408
- const registrationData = {
409
- key: appConfig.appKey,
410
- displayName: appConfig.displayName,
411
- configuration: {
412
- type: appConfig.appType,
413
- registryMode: appConfig.registryMode
414
- }
415
- };
416
-
417
- // Add optional fields only if they have values
418
- if (appConfig.description || options.description) {
419
- registrationData.description = appConfig.description || options.description;
420
- }
421
-
422
- if (appConfig.port) {
423
- registrationData.configuration.port = appConfig.port;
424
- }
425
-
426
134
  // Register application
135
+ const registrationData = buildRegistrationData(appConfig, options);
427
136
  const responseData = await callRegisterApi(
428
137
  authConfig.apiUrl,
429
138
  authConfig.token,
@@ -431,35 +140,8 @@ async function registerApplication(appKey, options) {
431
140
  registrationData
432
141
  );
433
142
 
434
- // Save credentials to local secrets if localhost
435
- if (isLocalhost(authConfig.apiUrl)) {
436
- const registeredAppKey = responseData.application.key;
437
- const clientIdKey = `${registeredAppKey}-client-idKeyVault`;
438
- const clientSecretKey = `${registeredAppKey}-client-secretKeyVault`;
439
-
440
- try {
441
- await saveLocalSecret(clientIdKey, responseData.credentials.clientId);
442
- await saveLocalSecret(clientSecretKey, responseData.credentials.clientSecret);
443
-
444
- // Update env.template
445
- await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, authConfig.apiUrl);
446
-
447
- // Regenerate .env file with updated credentials
448
- try {
449
- await generateEnvFile(registeredAppKey, null, 'local');
450
- logger.log(chalk.green('āœ“ .env file updated with new credentials'));
451
- } catch (error) {
452
- logger.warn(chalk.yellow(`āš ļø Could not regenerate .env file: ${error.message}`));
453
- }
454
-
455
- logger.log(chalk.green('\nāœ“ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
456
- logger.log(chalk.green('āœ“ env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
457
- } catch (error) {
458
- logger.warn(chalk.yellow(`āš ļø Could not save credentials locally: ${error.message}`));
459
- }
460
- }
461
-
462
- // Display results
143
+ // Save credentials and display results
144
+ await saveLocalCredentials(responseData, authConfig.apiUrl);
463
145
  displayRegistrationResults(responseData, authConfig.apiUrl, environment);
464
146
  }
465
147
 
package/lib/templates.js CHANGED
@@ -20,11 +20,16 @@ function generateVariablesYaml(appName, config) {
20
20
 
21
21
  // For external type, create minimal variables.yaml
22
22
  if (appType === 'external') {
23
+ // Use config values if provided, otherwise use defaults consistent with prompts
24
+ const systemKey = config.systemKey || appName;
25
+ const systemDisplayName = config.systemDisplayName || displayName;
26
+ const systemDescription = config.systemDescription || `External system integration for ${appName}`;
27
+
23
28
  const variables = {
24
29
  app: {
25
- key: appName,
26
- displayName: displayName,
27
- description: `${appName.replace(/-/g, ' ')} external system`,
30
+ key: systemKey,
31
+ displayName: systemDisplayName,
32
+ description: systemDescription,
28
33
  type: 'external'
29
34
  },
30
35
  deployment: {