@aifabrix/builder 2.10.1 → 2.20.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 (47) hide show
  1. package/.cursor/rules/project-rules.mdc +194 -0
  2. package/README.md +12 -0
  3. package/integration/hubspot/README.md +2 -2
  4. package/integration/hubspot/hubspot-deploy.json +12 -4
  5. package/lib/api/applications.api.js +164 -0
  6. package/lib/api/auth.api.js +304 -0
  7. package/lib/api/datasources-core.api.js +87 -0
  8. package/lib/api/datasources-extended.api.js +117 -0
  9. package/lib/api/datasources.api.js +13 -0
  10. package/lib/api/deployments.api.js +126 -0
  11. package/lib/api/environments.api.js +245 -0
  12. package/lib/api/external-systems.api.js +251 -0
  13. package/lib/api/index.js +221 -0
  14. package/lib/api/pipeline.api.js +234 -0
  15. package/lib/api/types/applications.types.js +136 -0
  16. package/lib/api/types/auth.types.js +218 -0
  17. package/lib/api/types/datasources.types.js +272 -0
  18. package/lib/api/types/deployments.types.js +184 -0
  19. package/lib/api/types/environments.types.js +197 -0
  20. package/lib/api/types/external-systems.types.js +244 -0
  21. package/lib/api/types/pipeline.types.js +125 -0
  22. package/lib/app-list.js +5 -7
  23. package/lib/app-register.js +70 -403
  24. package/lib/app-rotate-secret.js +4 -10
  25. package/lib/commands/login.js +19 -12
  26. package/lib/datasource-deploy.js +7 -30
  27. package/lib/datasource-list.js +9 -6
  28. package/lib/deployer.js +103 -135
  29. package/lib/environment-deploy.js +15 -26
  30. package/lib/external-system-deploy.js +12 -39
  31. package/lib/external-system-download.js +5 -13
  32. package/lib/external-system-test.js +9 -12
  33. package/lib/utils/api-error-handler.js +11 -453
  34. package/lib/utils/app-register-api.js +66 -0
  35. package/lib/utils/app-register-auth.js +72 -0
  36. package/lib/utils/app-register-config.js +205 -0
  37. package/lib/utils/app-register-display.js +69 -0
  38. package/lib/utils/app-register-validator.js +143 -0
  39. package/lib/utils/deployment-errors.js +88 -6
  40. package/lib/utils/device-code.js +1 -1
  41. package/lib/utils/error-formatters/error-parser.js +150 -0
  42. package/lib/utils/error-formatters/http-status-errors.js +189 -0
  43. package/lib/utils/error-formatters/network-errors.js +46 -0
  44. package/lib/utils/error-formatters/permission-errors.js +94 -0
  45. package/lib/utils/error-formatters/validation-errors.js +133 -0
  46. package/package.json +1 -1
  47. package/templates/applications/README.md.hbs +1 -1
@@ -11,459 +11,16 @@
11
11
  */
12
12
 
13
13
  const chalk = require('chalk');
14
- /**
15
- * Extracts missing permissions from error data
16
- * @param {Object} errorData - Error response data
17
- * @returns {Array<string>} Array of missing permissions
18
- */
19
- function extractMissingPermissions(errorData) {
20
- if (errorData.missingPermissions && Array.isArray(errorData.missingPermissions)) {
21
- return errorData.missingPermissions;
22
- }
23
- if (errorData.missing && errorData.missing.permissions && Array.isArray(errorData.missing.permissions)) {
24
- return errorData.missing.permissions;
25
- }
26
- return [];
27
- }
28
- /**
29
- * Extracts required permissions from error data
30
- * @param {Object} errorData - Error response data
31
- * @returns {Array<string>} Array of required permissions
32
- */
33
- function extractRequiredPermissions(errorData) {
34
- if (errorData.requiredPermissions && Array.isArray(errorData.requiredPermissions)) {
35
- return errorData.requiredPermissions;
36
- }
37
- if (errorData.required && errorData.required.permissions && Array.isArray(errorData.required.permissions)) {
38
- return errorData.required.permissions;
39
- }
40
- return [];
41
- }
42
- /**
43
- * Adds permission list to lines array
44
- * @param {Array<string>} lines - Lines array to append to
45
- * @param {Array<string>} perms - Permissions array
46
- * @param {string} label - Label for the permissions list
47
- */
48
- function addPermissionList(lines, perms, label) {
49
- if (perms.length > 0) {
50
- lines.push(chalk.yellow(`${label}:`));
51
- perms.forEach(perm => {
52
- lines.push(chalk.gray(` - ${perm}`));
53
- });
54
- lines.push('');
55
- }
56
- }
57
- /**
58
- * Formats permission error with missing and required permissions
59
- * @param {Object} errorData - Error response data
60
- * @returns {string} Formatted permission error message
61
- */
62
- function formatPermissionError(errorData) {
63
- const lines = [];
64
- lines.push(chalk.red('❌ Permission Denied\n'));
65
-
66
- if (errorData.detail) {
67
- lines.push(chalk.yellow(errorData.detail));
68
- lines.push('');
69
- }
70
-
71
- const missingPerms = extractMissingPermissions(errorData);
72
- addPermissionList(lines, missingPerms, 'Missing permissions');
73
-
74
- const requiredPerms = extractRequiredPermissions(errorData);
75
- addPermissionList(lines, requiredPerms, 'Required permissions');
76
- const requestUrl = errorData.instance || errorData.url;
77
- const method = errorData.method || 'POST';
78
- if (requestUrl) {
79
- lines.push(chalk.gray(`Request: ${method} ${requestUrl}`));
80
- }
81
- if (errorData.correlationId) {
82
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
83
- }
84
-
85
- return lines.join('\n');
86
- }
87
- /**
88
- * Formats validation error with field-level details
89
- * Handles unified error API response format (RFC 7807 Problem Details)
90
- * @param {Object} errorData - Error response data
91
- * @returns {string} Formatted validation error message
92
- */
93
- function formatValidationError(errorData) {
94
- const lines = [];
95
- lines.push(chalk.red('❌ Validation Error\n'));
96
-
97
- // Handle RFC 7807 Problem Details format
98
- // Priority: detail > title > errorDescription > message > error
99
- if (errorData.detail) {
100
- lines.push(chalk.yellow(errorData.detail));
101
- lines.push('');
102
- } else if (errorData.title) {
103
- lines.push(chalk.yellow(errorData.title));
104
- lines.push('');
105
- } else if (errorData.errorDescription) {
106
- // Handle Keycloak-style error format
107
- lines.push(chalk.yellow(errorData.errorDescription));
108
- if (errorData.error) {
109
- lines.push(chalk.gray(`Error code: ${errorData.error}`));
110
- }
111
- lines.push('');
112
- } else if (errorData.message) {
113
- lines.push(chalk.yellow(errorData.message));
114
- lines.push('');
115
- } else if (errorData.error) {
116
- lines.push(chalk.yellow(errorData.error));
117
- lines.push('');
118
- }
119
- // Handle errors array - this is the most important part
120
- if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
121
- lines.push(chalk.yellow('Validation errors:'));
122
- errorData.errors.forEach(err => {
123
- const field = err.field || err.path || 'validation';
124
- const message = err.message || 'Invalid value';
125
- // If field is 'validation' or generic, just show the message
126
- if (field === 'validation' || field === 'unknown') {
127
- lines.push(chalk.gray(` • ${message}`));
128
- } else {
129
- lines.push(chalk.gray(` • ${field}: ${message}`));
130
- }
131
- });
132
- lines.push('');
133
- }
134
- // Show instance (endpoint) if available (RFC 7807)
135
- if (errorData.instance) {
136
- lines.push(chalk.gray(`Endpoint: ${errorData.instance}`));
137
- }
138
- // Show correlation ID if available
139
- if (errorData.correlationId) {
140
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
141
- }
142
- return lines.join('\n');
143
- }
144
- /**
145
- * Formats authentication error
146
- * @param {Object} errorData - Error response data
147
- * @returns {string} Formatted authentication error message
148
- */
149
- function formatAuthenticationError(errorData) {
150
- const lines = [];
151
- lines.push(chalk.red('❌ Authentication Failed\n'));
152
- if (errorData.message) {
153
- lines.push(chalk.yellow(errorData.message));
154
- } else {
155
- lines.push(chalk.yellow('Invalid credentials or token expired.'));
156
- }
157
- lines.push('');
158
- lines.push(chalk.gray('Run: aifabrix login'));
159
- if (errorData.correlationId) {
160
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
161
- }
162
- return lines.join('\n');
163
- }
164
- /**
165
- * Formats network error
166
- * @param {string} errorMessage - Error message
167
- * @param {Object} errorData - Error response data (optional)
168
- * @returns {string} Formatted network error message
169
- */
170
- function formatNetworkError(errorMessage, errorData) {
171
- const lines = [];
172
- lines.push(chalk.red('❌ Network Error\n'));
173
-
174
- if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('Cannot connect')) {
175
- lines.push(chalk.yellow('Cannot connect to controller.'));
176
- lines.push(chalk.gray('Check if the controller is running.'));
177
- } else if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('hostname not found')) {
178
- lines.push(chalk.yellow('Controller hostname not found.'));
179
- lines.push(chalk.gray('Check your controller URL.'));
180
- } else if (errorMessage.includes('timeout') || errorMessage.includes('timed out')) {
181
- lines.push(chalk.yellow('Request timed out.'));
182
- lines.push(chalk.gray('The controller may be overloaded.'));
183
- } else {
184
- lines.push(chalk.yellow(errorMessage));
185
- }
186
-
187
- if (errorData && errorData.correlationId) {
188
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
189
- }
190
-
191
- return lines.join('\n');
192
- }
193
-
194
- /**
195
- * Formats server error (500+)
196
- * @param {Object} errorData - Error response data
197
- * @returns {string} Formatted server error message
198
- */
199
- function formatServerError(errorData) {
200
- const lines = [];
201
- lines.push(chalk.red('❌ Server Error\n'));
202
-
203
- // Check for RFC 7807 Problem Details format (detail field)
204
- if (errorData.detail) {
205
- lines.push(chalk.yellow(errorData.detail));
206
- } else if (errorData.message) {
207
- lines.push(chalk.yellow(errorData.message));
208
- } else {
209
- lines.push(chalk.yellow('An internal server error occurred.'));
210
- }
211
- lines.push('');
212
- lines.push(chalk.gray('Please try again later or contact support.'));
213
-
214
- if (errorData.correlationId) {
215
- lines.push('');
216
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
217
- }
218
-
219
- return lines.join('\n');
220
- }
221
-
222
- /**
223
- * Formats conflict error (409)
224
- * @param {Object} errorData - Error response data
225
- * @returns {string} Formatted conflict error message
226
- */
227
- function formatConflictError(errorData) {
228
- const lines = [];
229
- lines.push(chalk.red('❌ Conflict\n'));
230
-
231
- // Check if it's an application already exists error
232
- const detail = errorData.detail || errorData.message || '';
233
- if (detail.toLowerCase().includes('application already exists')) {
234
- lines.push(chalk.yellow('This application already exists in this environment.'));
235
- lines.push('');
236
- lines.push(chalk.gray('Options:'));
237
- lines.push(chalk.gray(' • Use a different environment'));
238
- lines.push(chalk.gray(' • Check existing applications: aifabrix app list -e <environment>'));
239
- lines.push(chalk.gray(' • Update the existing application if needed'));
240
- } else if (detail) {
241
- lines.push(chalk.yellow(detail));
242
- } else if (errorData.message) {
243
- lines.push(chalk.yellow(errorData.message));
244
- } else {
245
- lines.push(chalk.yellow('A conflict occurred. The resource may already exist.'));
246
- }
247
-
248
- if (errorData.correlationId) {
249
- lines.push('');
250
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
251
- }
252
-
253
- return lines.join('\n');
254
- }
255
-
256
- /**
257
- * Gets actionable guidance options based on error detail
258
- * @param {string} detail - Error detail message
259
- * @returns {Array<string>} Array of guidance options
260
- */
261
- function getNotFoundGuidance(detail) {
262
- const lowerDetail = detail.toLowerCase();
263
- if (lowerDetail.includes('environment')) {
264
- return [
265
- ' • Check the environment key spelling',
266
- ' • List available environments: aifabrix app list -e <environment>',
267
- ' • Verify you have access to this environment'
268
- ];
269
- }
270
- if (lowerDetail.includes('application')) {
271
- return [
272
- ' • Check the application key spelling',
273
- ' • List applications: aifabrix app list -e <environment>',
274
- ' • Verify the application exists in this environment'
275
- ];
276
- }
277
- return [
278
- ' • Check the resource identifier',
279
- ' • Verify the resource exists',
280
- ' • Check your permissions'
281
- ];
282
- }
283
-
284
- /**
285
- * Formats not found error (404)
286
- * @param {Object} errorData - Error response data
287
- * @returns {string} Formatted not found error message
288
- */
289
- function formatNotFoundError(errorData) {
290
- const lines = [];
291
- lines.push(chalk.red('❌ Not Found\n'));
292
-
293
- const detail = errorData.detail || errorData.message || '';
294
- if (detail) {
295
- lines.push(chalk.yellow(detail));
296
- lines.push('');
297
- }
298
-
299
- lines.push(chalk.gray('Options:'));
300
- const guidance = getNotFoundGuidance(detail);
301
- guidance.forEach(option => {
302
- lines.push(chalk.gray(option));
303
- });
304
-
305
- if (errorData.correlationId) {
306
- lines.push('');
307
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
308
- }
309
-
310
- return lines.join('\n');
311
- }
312
-
313
- /**
314
- * Formats generic error
315
- * @param {Object} errorData - Error response data
316
- * @param {number} statusCode - HTTP status code
317
- * @returns {string} Formatted error message
318
- */
319
- function formatGenericError(errorData, statusCode) {
320
- const lines = [];
321
- lines.push(chalk.red(`❌ Error (HTTP ${statusCode})\n`));
322
-
323
- // Check for RFC 7807 Problem Details format (detail field)
324
- if (errorData.detail) {
325
- lines.push(chalk.yellow(errorData.detail));
326
- } else if (errorData.message) {
327
- lines.push(chalk.yellow(errorData.message));
328
- } else if (errorData.error) {
329
- lines.push(chalk.yellow(errorData.error));
330
- } else {
331
- lines.push(chalk.yellow('An error occurred while processing your request.'));
332
- }
333
-
334
- if (errorData.correlationId) {
335
- lines.push('');
336
- lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
337
- }
338
-
339
- return lines.join('\n');
340
- }
341
-
342
- /**
343
- * Parses error response into error data object
344
- * @param {string|Object} errorResponse - Error response (string or parsed JSON)
345
- * @returns {Object} Parsed error data object
346
- */
347
- function parseErrorData(errorResponse) {
348
- if (errorResponse === undefined || errorResponse === null) {
349
- return { message: 'Unknown error occurred' };
350
- }
351
- if (typeof errorResponse === 'string') {
352
- try {
353
- return JSON.parse(errorResponse);
354
- } catch {
355
- return { message: errorResponse };
356
- }
357
- }
358
- if (typeof errorResponse === 'object') {
359
- return errorResponse;
360
- }
361
- return { message: String(errorResponse) };
362
- }
363
-
364
- /**
365
- * Creates error result object
366
- * @param {string} type - Error type
367
- * @param {string} message - Error message
368
- * @param {string} formatted - Formatted error message
369
- * @param {Object} data - Error data
370
- * @returns {Object} Error result object
371
- */
372
- function createErrorResult(type, message, formatted, data) {
373
- return { type, message, formatted, data };
374
- }
375
-
376
- /**
377
- * Gets error message from error data
378
- * @param {Object} errorData - Error data object
379
- * @param {string} defaultMessage - Default message if not found
380
- * @returns {string} Error message
381
- */
382
- function getErrorMessage(errorData, defaultMessage) {
383
- return errorData.detail || errorData.title || errorData.errorDescription || errorData.message || errorData.error || defaultMessage;
384
- }
385
-
386
- /**
387
- * Handles 400 validation error
388
- * @param {Object} errorData - Error data object
389
- * @returns {Object} Error result object
390
- */
391
- function handleValidationError(errorData) {
392
- const errorMessage = getErrorMessage(errorData, 'Validation error');
393
- return createErrorResult('validation', errorMessage, formatValidationError(errorData), errorData);
394
- }
395
-
396
- /**
397
- * Handles specific 4xx client error codes
398
- * @param {number} statusCode - HTTP status code
399
- * @param {Object} errorData - Error data object
400
- * @returns {Object|null} Error result object or null if not handled
401
- */
402
- function handleSpecificClientErrors(statusCode, errorData) {
403
- switch (statusCode) {
404
- case 403:
405
- return createErrorResult('permission', 'Permission denied', formatPermissionError(errorData), errorData);
406
- case 401:
407
- return createErrorResult('authentication', 'Authentication failed', formatAuthenticationError(errorData), errorData);
408
- case 400:
409
- return handleValidationError(errorData);
410
- case 404:
411
- return createErrorResult('notfound', getErrorMessage(errorData, 'Not found'), formatNotFoundError(errorData), errorData);
412
- case 409:
413
- return createErrorResult('conflict', getErrorMessage(errorData, 'Conflict'), formatConflictError(errorData), errorData);
414
- default:
415
- return null;
416
- }
417
- }
418
-
419
- /**
420
- * Handles HTTP status code errors
421
- * @param {number} statusCode - HTTP status code
422
- * @param {Object} errorData - Error data object
423
- * @returns {Object|null} Error result object or null if not handled
424
- */
425
- function handleStatusCodeError(statusCode, errorData) {
426
- // Handle 4xx client errors
427
- if (statusCode >= 400 && statusCode < 500) {
428
- return handleSpecificClientErrors(statusCode, errorData);
429
- }
430
- // Handle 5xx server errors
431
- if (statusCode >= 500) {
432
- return createErrorResult('server', 'Server error', formatServerError(errorData), errorData);
433
- }
434
- return null;
435
- }
436
-
437
- /**
438
- * Parses error response and determines error type
439
- * @param {string|Object} errorResponse - Error response (string or parsed JSON)
440
- * @param {number} statusCode - HTTP status code
441
- * @param {boolean} isNetworkError - Whether this is a network error
442
- * @returns {Object} Parsed error object with type, message, and formatted output
443
- */
444
- function parseErrorResponse(errorResponse, statusCode, isNetworkError) {
445
- let errorData = parseErrorData(errorResponse);
446
-
447
- // Handle nested response structure (some APIs wrap errors in data field)
448
- if (errorData.data && typeof errorData.data === 'object') {
449
- errorData = errorData.data;
450
- }
451
-
452
- // Handle network errors
453
- if (isNetworkError) {
454
- const errorMessage = errorData.message || errorResponse || 'Network error';
455
- return createErrorResult('network', errorMessage, formatNetworkError(errorMessage, errorData), errorData);
456
- }
457
-
458
- // Handle HTTP status codes
459
- const statusError = handleStatusCodeError(statusCode, errorData);
460
- if (statusError) {
461
- return statusError;
462
- }
463
-
464
- // Generic error
465
- return createErrorResult('generic', errorData.message || errorData.error || 'Unknown error', formatGenericError(errorData, statusCode), errorData);
466
- }
14
+ const { parseErrorResponse } = require('./error-formatters/error-parser');
15
+ const { formatPermissionError } = require('./error-formatters/permission-errors');
16
+ const { formatValidationError } = require('./error-formatters/validation-errors');
17
+ const {
18
+ formatAuthenticationError,
19
+ formatServerError,
20
+ formatConflictError,
21
+ formatGenericError
22
+ } = require('./error-formatters/http-status-errors');
23
+ const { formatNetworkError } = require('./error-formatters/network-errors');
467
24
 
468
25
  /**
469
26
  * Formats error for display in CLI
@@ -487,6 +44,7 @@ function formatApiError(apiResponse) {
487
44
  const parsed = parseErrorResponse(errorResponse, statusCode, isNetworkError);
488
45
  return parsed.formatted;
489
46
  }
47
+
490
48
  module.exports = {
491
49
  parseErrorResponse,
492
50
  formatApiError,
@@ -0,0 +1,66 @@
1
+ /**
2
+ * AI Fabrix Builder - App Register API Utilities
3
+ *
4
+ * API call utilities for application registration
5
+ *
6
+ * @fileoverview API utilities for app registration
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('./logger');
13
+ const { registerApplication } = require('../api/applications.api');
14
+ const { formatApiError } = require('./api-error-handler');
15
+
16
+ /**
17
+ * Call registration API
18
+ * @async
19
+ * @param {string} apiUrl - API URL
20
+ * @param {string} token - Authentication token
21
+ * @param {string} environment - Environment ID
22
+ * @param {Object} registrationData - Registration data
23
+ * @returns {Promise<Object>} API response
24
+ */
25
+ async function callRegisterApi(apiUrl, token, environment, registrationData) {
26
+ // Use centralized API client
27
+ const authConfig = { type: 'bearer', token: token };
28
+ const response = await registerApplication(apiUrl, environment, authConfig, registrationData);
29
+
30
+ if (!response.success) {
31
+ const formattedError = response.formattedError || formatApiError(response);
32
+ logger.error(formattedError);
33
+
34
+ // For validation errors (400, 422), show the request payload for debugging
35
+ if (response.status === 400 || response.status === 422) {
36
+ logger.error(chalk.gray('\nRequest payload:'));
37
+ logger.error(chalk.gray(JSON.stringify(registrationData, null, 2)));
38
+ logger.error('');
39
+ logger.error(chalk.gray('Check your variables.yaml file and ensure all required fields are correctly set.'));
40
+ }
41
+
42
+ process.exit(1);
43
+ }
44
+
45
+ // Handle API response structure:
46
+ // registerApplication returns: { success: true, data: <API response> }
47
+ // API response can be:
48
+ // 1. Direct format: { application: {...}, credentials: {...} }
49
+ // 2. Wrapped format: { success: true, data: { application: {...}, credentials: {...} } }
50
+ const apiResponse = response.data;
51
+ if (apiResponse && apiResponse.data && apiResponse.data.application) {
52
+ // Wrapped format: use apiResponse.data
53
+ return apiResponse.data;
54
+ } else if (apiResponse && apiResponse.application) {
55
+ // Direct format: use apiResponse directly
56
+ return apiResponse;
57
+ }
58
+ // Fallback: return apiResponse as-is (shouldn't happen, but handle gracefully)
59
+ logger.error(chalk.red('❌ Invalid response: missing application data'));
60
+ logger.error(chalk.gray('\nFull response for debugging:'));
61
+ logger.error(chalk.gray(JSON.stringify(response, null, 2)));
62
+ process.exit(1);
63
+ }
64
+
65
+ module.exports = { callRegisterApi };
66
+
@@ -0,0 +1,72 @@
1
+ /**
2
+ * AI Fabrix Builder - App Register Authentication Utilities
3
+ *
4
+ * Authentication utilities for application registration
5
+ *
6
+ * @fileoverview Authentication utilities for app registration
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('./logger');
13
+ const { getConfig } = require('../config');
14
+ const { getOrRefreshDeviceToken } = require('./token-manager');
15
+
16
+ /**
17
+ * Check if user is authenticated and get token
18
+ * @async
19
+ * @param {string} [controllerUrl] - Optional controller URL from variables.yaml
20
+ * @param {string} [environment] - Optional environment key
21
+ * @returns {Promise<{apiUrl: string, token: string}>} Configuration with API URL and token
22
+ */
23
+ async function checkAuthentication(controllerUrl, environment) {
24
+ const config = await getConfig();
25
+
26
+ // Try to get controller URL from parameter, config, or device tokens
27
+ let finalControllerUrl = controllerUrl;
28
+ let token = null;
29
+
30
+ // If controller URL provided, try to get device token
31
+ if (finalControllerUrl) {
32
+ const deviceToken = await getOrRefreshDeviceToken(finalControllerUrl);
33
+ if (deviceToken && deviceToken.token) {
34
+ token = deviceToken.token;
35
+ finalControllerUrl = deviceToken.controller;
36
+ }
37
+ }
38
+
39
+ // If no token yet, try to find any device token in config
40
+ if (!token && config.device) {
41
+ const deviceUrls = Object.keys(config.device);
42
+ if (deviceUrls.length > 0) {
43
+ // Use first available device token
44
+ finalControllerUrl = deviceUrls[0];
45
+ const deviceToken = await getOrRefreshDeviceToken(finalControllerUrl);
46
+ if (deviceToken && deviceToken.token) {
47
+ token = deviceToken.token;
48
+ finalControllerUrl = deviceToken.controller;
49
+ }
50
+ }
51
+ }
52
+
53
+ // If still no token, check for client token (requires environment and app)
54
+ if (!token && environment) {
55
+ // For app register, we don't have an app yet, so client tokens won't work
56
+ // This is expected - device tokens should be used for registration
57
+ }
58
+
59
+ if (!token || !finalControllerUrl) {
60
+ logger.error(chalk.red('❌ Not logged in. Run: aifabrix login'));
61
+ logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
62
+ process.exit(1);
63
+ }
64
+
65
+ return {
66
+ apiUrl: finalControllerUrl,
67
+ token: token
68
+ };
69
+ }
70
+
71
+ module.exports = { checkAuthentication };
72
+