@aifabrix/builder 2.45.0 → 2.45.5

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 (180) hide show
  1. package/.cursor/rules/docs-rules.mdc +3 -1
  2. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  4. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  5. package/jest.projects.js +44 -1
  6. package/lib/api/agent-metadata-validation.api.js +93 -0
  7. package/lib/api/audit-api-helpers.js +68 -0
  8. package/lib/api/audit.api.js +119 -0
  9. package/lib/api/auth-cache.api.js +44 -0
  10. package/lib/api/certificates.api.js +4 -4
  11. package/lib/api/credential.api.js +2 -2
  12. package/lib/api/dataplane-health.api.js +137 -0
  13. package/lib/api/dataplane-sync.api.js +27 -0
  14. package/lib/api/datasources-core.api.js +21 -18
  15. package/lib/api/datasources-extended.api.js +22 -22
  16. package/lib/api/dimensions.api.js +3 -0
  17. package/lib/api/external-systems.api.js +13 -13
  18. package/lib/api/governance-scenarios.api.js +39 -0
  19. package/lib/api/groups.api.js +79 -0
  20. package/lib/api/index.js +45 -2
  21. package/lib/api/pipeline.api.js +4 -4
  22. package/lib/api/protection.api.js +157 -0
  23. package/lib/api/records-bulk.api.js +30 -0
  24. package/lib/api/records-search-parse.js +49 -0
  25. package/lib/api/records-search.api.js +29 -0
  26. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  27. package/lib/api/types/audit.types.js +72 -0
  28. package/lib/api/types/dataplane-health.types.js +27 -0
  29. package/lib/api/types/dimensions.types.js +2 -0
  30. package/lib/api/types/governance-scenarios.types.js +39 -0
  31. package/lib/api/types/protection.types.js +69 -0
  32. package/lib/api/types/records-bulk.types.js +35 -0
  33. package/lib/api/types/records-search.types.js +28 -0
  34. package/lib/api/user-groups.api.js +50 -0
  35. package/lib/api/users.api.js +95 -0
  36. package/lib/api/validation-run.api.js +3 -3
  37. package/lib/api/validation-runner.js +3 -3
  38. package/lib/api/wizard-platform.api.js +3 -3
  39. package/lib/api/wizard.api.js +21 -21
  40. package/lib/app/deploy.js +34 -19
  41. package/lib/cli/index.js +4 -2
  42. package/lib/cli/infra-guided.js +22 -3
  43. package/lib/cli/setup-app.help.js +50 -2
  44. package/lib/cli/setup-app.js +33 -24
  45. package/lib/cli/setup-app.test-commands.js +80 -21
  46. package/lib/cli/setup-auth.js +30 -3
  47. package/lib/cli/setup-external-system.js +37 -3
  48. package/lib/cli/setup-utility-help-after.js +45 -0
  49. package/lib/cli/setup-utility-resolve.js +2 -2
  50. package/lib/cli/setup-utility.js +99 -81
  51. package/lib/commands/auth-status-dataplane-version.js +231 -0
  52. package/lib/commands/auth-status-display.js +94 -0
  53. package/lib/commands/auth-status.js +70 -6
  54. package/lib/commands/credential-push.js +10 -5
  55. package/lib/commands/datasource-load-export-cli.js +178 -0
  56. package/lib/commands/datasource-test-trust-cli.js +114 -0
  57. package/lib/commands/datasource-unified-test-cli.js +25 -17
  58. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  59. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  60. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  61. package/lib/commands/datasource.js +68 -1
  62. package/lib/commands/dimension-value.js +43 -13
  63. package/lib/commands/dimension.js +38 -7
  64. package/lib/commands/identity-apply-sync.js +131 -0
  65. package/lib/commands/identity-group-cli.js +146 -0
  66. package/lib/commands/identity-membership-role-cache.js +175 -0
  67. package/lib/commands/identity-shared.js +94 -0
  68. package/lib/commands/identity-user-cli.js +158 -0
  69. package/lib/commands/identity.js +43 -0
  70. package/lib/commands/login.js +46 -16
  71. package/lib/commands/protection-cli-leaves.js +286 -0
  72. package/lib/commands/protection.js +50 -0
  73. package/lib/commands/repair.js +13 -3
  74. package/lib/commands/test-e2e-external.js +40 -9
  75. package/lib/commands/test-governance-cli-options.js +47 -0
  76. package/lib/commands/test-governance-command-action.js +55 -0
  77. package/lib/commands/test-governance-external.js +88 -0
  78. package/lib/commands/test-trust-cli-options.js +32 -0
  79. package/lib/commands/test-trust-command-action.js +118 -0
  80. package/lib/commands/test-trust-external.js +127 -0
  81. package/lib/commands/upload.js +34 -13
  82. package/lib/commands/wizard-core.js +3 -0
  83. package/lib/core/config-device-dataplane.js +185 -0
  84. package/lib/datasource/agent-trust-debug-log.js +45 -0
  85. package/lib/datasource/agent-trust-fetch.js +115 -0
  86. package/lib/datasource/agent-trust-map.js +101 -0
  87. package/lib/datasource/agent-trust-mock.js +44 -0
  88. package/lib/datasource/agent-trust-run.js +93 -0
  89. package/lib/datasource/agent-trust-timeout.js +25 -0
  90. package/lib/datasource/audit-evidence-display.js +80 -0
  91. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  92. package/lib/datasource/audit-evidence-extract.js +177 -0
  93. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  94. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  95. package/lib/datasource/audit-evidence-poll.js +109 -0
  96. package/lib/datasource/audit-evidence-run.js +177 -0
  97. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  98. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  99. package/lib/datasource/audit-evidence-verify.js +108 -0
  100. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  101. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  102. package/lib/datasource/bulk-loader-service.js +127 -0
  103. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  104. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  105. package/lib/datasource/datasource-exporter-service.js +144 -0
  106. package/lib/datasource/datasource-identifier.js +148 -0
  107. package/lib/datasource/datasource-load-export-context.js +92 -0
  108. package/lib/datasource/integration-context.js +6 -4
  109. package/lib/datasource/local-data-paths.js +145 -0
  110. package/lib/datasource/log-cleaner.js +241 -0
  111. package/lib/datasource/log-viewer.js +111 -6
  112. package/lib/datasource/record-file-parser.js +85 -0
  113. package/lib/datasource/record-mapper.js +129 -0
  114. package/lib/datasource/resolve-app.js +74 -55
  115. package/lib/datasource/test-e2e.js +26 -2
  116. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  117. package/lib/datasource/unified-validation-run.js +43 -17
  118. package/lib/datasource/validate.js +37 -20
  119. package/lib/external-system/test-system-level.js +25 -4
  120. package/lib/external-system/test.js +3 -1
  121. package/lib/governance/governance-pack-loader.js +70 -0
  122. package/lib/governance/governance-run-display.js +119 -0
  123. package/lib/identity/identity-apply-core.js +108 -0
  124. package/lib/identity/identity-apply-phases.js +244 -0
  125. package/lib/identity/identity-apply-service.js +77 -0
  126. package/lib/identity/identity-csv-parser.js +114 -0
  127. package/lib/internal/fs-real-sync.js +29 -1
  128. package/lib/protection/auth-context.js +45 -0
  129. package/lib/protection/convert-batch.js +90 -0
  130. package/lib/protection/load.js +41 -0
  131. package/lib/protection/paths.js +158 -0
  132. package/lib/protection/preflight-datasource-ready.js +46 -0
  133. package/lib/protection/protection-create-probe-lines.js +142 -0
  134. package/lib/protection/protection-create-probes.js +95 -0
  135. package/lib/protection/protection-create-scaffold.js +281 -0
  136. package/lib/protection/protection-display-helpers.js +445 -0
  137. package/lib/protection/protection-display.js +215 -0
  138. package/lib/protection/protection-preset-registry.js +97 -0
  139. package/lib/protection/report-exit.js +62 -0
  140. package/lib/protection/resolve.js +202 -0
  141. package/lib/protection/run-commands-validate.js +130 -0
  142. package/lib/protection/run-commands.js +216 -0
  143. package/lib/protection/run-protection-create-helpers.js +134 -0
  144. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  145. package/lib/protection/run-protection-create.js +191 -0
  146. package/lib/protection/scope.js +22 -0
  147. package/lib/protection/sync-after-upload.js +64 -0
  148. package/lib/protection/upload-batch.js +127 -0
  149. package/lib/protection/validate-batch.js +113 -0
  150. package/lib/protection/validate-local.js +65 -0
  151. package/lib/resolvers/dimension-file.js +29 -4
  152. package/lib/schema/external-datasource.schema.json +47 -13
  153. package/lib/schema/flag-map-validation-run.json +2 -2
  154. package/lib/schema/protection.schema.json +374 -0
  155. package/lib/schema/type/credential-field-registry.json +636 -0
  156. package/lib/schema/wizard-config.schema.json +1 -1
  157. package/lib/utils/agent-trust-run-display.js +231 -0
  158. package/lib/utils/agent-trust-run-exit.js +59 -0
  159. package/lib/utils/agent-trust-wait-ui.js +94 -0
  160. package/lib/utils/cli-exit-codes.js +30 -0
  161. package/lib/utils/cli-sync-options.js +33 -0
  162. package/lib/utils/credential-push-ui.js +90 -0
  163. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  164. package/lib/utils/dataplane-cli-version-help.js +74 -0
  165. package/lib/utils/datasource-export-display.js +137 -0
  166. package/lib/utils/datasource-load-display.js +174 -0
  167. package/lib/utils/datasource-load-export-ui.js +120 -0
  168. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  169. package/lib/utils/datasource-test-run-display.js +3 -3
  170. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  171. package/lib/utils/help-builder.js +30 -24
  172. package/lib/utils/manifest-source-emit.js +22 -0
  173. package/lib/utils/semver-compare.js +127 -0
  174. package/lib/utils/upload-pipeline-ui.js +91 -0
  175. package/lib/utils/validation-poll-ui.js +14 -7
  176. package/lib/validation/wizard-config-validator.js +58 -19
  177. package/package.json +1 -1
  178. package/templates/applications/dataplane/env.template +13 -1
  179. package/templates/applications/dataplane/rbac.yaml +4 -0
  180. package/templates/external-system/README.md.hbs +56 -8
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @fileoverview Identity membership, role, and cache subcommands
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const logger = require('../utils/logger');
10
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
11
+ const {
12
+ resolveControllerAndAuth,
13
+ resolveEnvKey,
14
+ requireNonEmpty,
15
+ parseGroupsList,
16
+ unwrapControllerData,
17
+ throwIfApiFailed
18
+ } = require('./identity-shared');
19
+ const { addUserToGroup, removeUserFromGroup } = require('../api/user-groups.api');
20
+ const { listEnvironmentRoles, updateRoleGroups } = require('../api/environments.api');
21
+ const { clearAuthCache, invalidateAuthCache } = require('../api/auth-cache.api');
22
+
23
+ function printJson(data) {
24
+ logger.log(JSON.stringify(data, null, 2));
25
+ }
26
+
27
+ /**
28
+ * @param {import('commander').Command} memCmd
29
+ */
30
+ function setupIdentityMembershipCommands(memCmd) {
31
+ memCmd
32
+ .command('add <userId> <groupId>')
33
+ .description('Add user to group (controller ids)')
34
+ .action(async(userId, groupId) => {
35
+ try {
36
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
37
+ const res = await addUserToGroup(
38
+ controllerUrl,
39
+ authConfig,
40
+ requireNonEmpty(userId, 'userId'),
41
+ requireNonEmpty(groupId, 'groupId'),
42
+ {}
43
+ );
44
+ throwIfApiFailed(res);
45
+ logger.log(formatSuccessLine(`Membership created: ${userId} → ${groupId}`));
46
+ } catch (e) {
47
+ logger.error(formatBlockingError(e.message));
48
+ process.exit(1);
49
+ }
50
+ });
51
+
52
+ memCmd
53
+ .command('remove <userId> <groupId>')
54
+ .description('Remove user from group')
55
+ .action(async(userId, groupId) => {
56
+ try {
57
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
58
+ const res = await removeUserFromGroup(
59
+ controllerUrl,
60
+ authConfig,
61
+ requireNonEmpty(userId, 'userId'),
62
+ requireNonEmpty(groupId, 'groupId')
63
+ );
64
+ throwIfApiFailed(res);
65
+ logger.log(formatSuccessLine(`Membership removed: ${userId} → ${groupId}`));
66
+ } catch (e) {
67
+ logger.error(formatBlockingError(e.message));
68
+ process.exit(1);
69
+ }
70
+ });
71
+ }
72
+
73
+ /**
74
+ * @param {import('commander').Command} roleCmd
75
+ */
76
+ function setupIdentityRoleCommands(roleCmd) {
77
+ roleCmd
78
+ .command('list')
79
+ .description('List roles and group mappings for an environment')
80
+ .requiredOption('-e, --env <key>', 'Environment key (dev, tst, pro)')
81
+ .option('--json', 'JSON output')
82
+ .action(async(options) => {
83
+ try {
84
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
85
+ const envKey = await resolveEnvKey(options);
86
+ const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
87
+ throwIfApiFailed(res);
88
+ const data = unwrapControllerData(res);
89
+ if (options.json) {
90
+ printJson(data);
91
+ return;
92
+ }
93
+ const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
94
+ (items || []).forEach((r) => {
95
+ const groups = Array.isArray(r.groups) ? r.groups.join(', ') : '';
96
+ logger.log(` ${r.value || r.role || r.name} → ${groups || '—'}`);
97
+ });
98
+ logger.log('');
99
+ } catch (e) {
100
+ logger.error(formatBlockingError(e.message));
101
+ process.exit(1);
102
+ }
103
+ });
104
+
105
+ roleCmd
106
+ .command('set-groups <roleValue>')
107
+ .description('Map role to groups in an environment')
108
+ .requiredOption('-e, --env <key>', 'Environment key')
109
+ .requiredOption('--groups <names>', 'Comma-separated group names')
110
+ .action(async(roleValue, options) => {
111
+ try {
112
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
113
+ const envKey = await resolveEnvKey(options);
114
+ const groups = parseGroupsList(options.groups);
115
+ const res = await updateRoleGroups(
116
+ controllerUrl,
117
+ envKey,
118
+ requireNonEmpty(roleValue, 'roleValue'),
119
+ authConfig,
120
+ groups
121
+ );
122
+ throwIfApiFailed(res);
123
+ logger.log(formatSuccessLine(`Role ${roleValue} mapped to ${groups.length} group(s) in ${envKey}`));
124
+ } catch (e) {
125
+ logger.error(formatBlockingError(e.message));
126
+ process.exit(1);
127
+ }
128
+ });
129
+ }
130
+
131
+ /**
132
+ * @param {import('commander').Command} cacheCmd
133
+ */
134
+ function setupIdentityCacheCommands(cacheCmd) {
135
+ cacheCmd
136
+ .command('clear')
137
+ .description('Clear all controller auth/RBAC cache entries')
138
+ .action(async() => {
139
+ try {
140
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
141
+ const res = await clearAuthCache(controllerUrl, authConfig);
142
+ throwIfApiFailed(res);
143
+ logger.log(formatSuccessLine('Controller cache cleared'));
144
+ } catch (e) {
145
+ logger.error(formatBlockingError(e.message));
146
+ process.exit(1);
147
+ }
148
+ });
149
+
150
+ cacheCmd
151
+ .command('invalidate')
152
+ .description('Invalidate controller cache entries by pattern')
153
+ .requiredOption('--pattern <pattern>', 'Cache key pattern (e.g. permissions:*)')
154
+ .action(async(options) => {
155
+ try {
156
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
157
+ const res = await invalidateAuthCache(
158
+ controllerUrl,
159
+ authConfig,
160
+ requireNonEmpty(options.pattern, 'pattern')
161
+ );
162
+ throwIfApiFailed(res);
163
+ logger.log(formatSuccessLine(`Cache invalidated for pattern: ${options.pattern}`));
164
+ } catch (e) {
165
+ logger.error(formatBlockingError(e.message));
166
+ process.exit(1);
167
+ }
168
+ });
169
+ }
170
+
171
+ module.exports = {
172
+ setupIdentityMembershipCommands,
173
+ setupIdentityRoleCommands,
174
+ setupIdentityCacheCommands
175
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @fileoverview Shared auth/helpers for identity CLI commands
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { resolveControllerUrl } = require('../utils/controller-url');
10
+ const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
11
+ const { getOrRefreshDeviceToken } = require('../utils/token-manager');
12
+ const {
13
+ unwrapControllerData,
14
+ throwIfApiFailed,
15
+ assertSyncStatsOk,
16
+ apiErrorMessage
17
+ } = require('../identity/identity-apply-core');
18
+
19
+ /**
20
+ * @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
21
+ */
22
+ async function resolveControllerAndAuth() {
23
+ const controllerUrl = await resolveControllerUrl();
24
+ if (!controllerUrl) {
25
+ throw new Error('Controller URL is required. Run "aifabrix login" first.');
26
+ }
27
+ const normalized = normalizeControllerUrl(controllerUrl);
28
+ const deviceToken = await getOrRefreshDeviceToken(normalized);
29
+ if (!deviceToken || !deviceToken.token) {
30
+ throw new Error(
31
+ `Not authenticated for controller: ${controllerUrl}. Run "aifabrix login" and try again.`
32
+ );
33
+ }
34
+ return {
35
+ controllerUrl: deviceToken.controller || normalized,
36
+ authConfig: { type: 'bearer', token: deviceToken.token }
37
+ };
38
+ }
39
+
40
+ /**
41
+ * @param {Object} [options]
42
+ * @returns {Promise<string>}
43
+ */
44
+ async function resolveEnvKey(options = {}) {
45
+ const fromFlag = options.env || options.environment;
46
+ if (fromFlag) {
47
+ return String(fromFlag).trim();
48
+ }
49
+ const env = await resolveEnvironment();
50
+ return env || 'dev';
51
+ }
52
+
53
+ /**
54
+ * @param {string} raw
55
+ * @param {string} label
56
+ * @returns {string}
57
+ */
58
+ function requireNonEmpty(raw, label) {
59
+ const s = String(raw || '').trim();
60
+ if (!s) {
61
+ throw new Error(`${label} is required.`);
62
+ }
63
+ return s;
64
+ }
65
+
66
+ /**
67
+ * @param {string} groupsArg
68
+ * @returns {string[]}
69
+ */
70
+ function parseGroupsList(groupsArg) {
71
+ const raw = String(groupsArg || '').trim();
72
+ if (!raw) {
73
+ throw new Error('--groups is required (comma-separated group names).');
74
+ }
75
+ const parts = raw
76
+ .split(',')
77
+ .map((g) => g.trim())
78
+ .filter(Boolean);
79
+ if (parts.length === 0) {
80
+ throw new Error('At least one group name is required in --groups.');
81
+ }
82
+ return parts;
83
+ }
84
+
85
+ module.exports = {
86
+ resolveControllerAndAuth,
87
+ resolveEnvKey,
88
+ requireNonEmpty,
89
+ parseGroupsList,
90
+ unwrapControllerData,
91
+ throwIfApiFailed,
92
+ assertSyncStatsOk,
93
+ apiErrorMessage
94
+ };
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @fileoverview Identity user subcommands
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('../utils/logger');
11
+ const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
12
+ const {
13
+ resolveControllerAndAuth,
14
+ requireNonEmpty,
15
+ unwrapControllerData,
16
+ throwIfApiFailed
17
+ } = require('./identity-shared');
18
+ const {
19
+ listUsers,
20
+ getUser,
21
+ createUser,
22
+ listUserGroups
23
+ } = require('../api/users.api');
24
+
25
+ function printJson(data) {
26
+ logger.log(JSON.stringify(data, null, 2));
27
+ }
28
+
29
+ function registerUserCreate(userCmd) {
30
+ userCmd
31
+ .command('create')
32
+ .description('Create a user on the controller')
33
+ .requiredOption('--email <email>', 'User email')
34
+ .option('--first-name <name>', 'First name')
35
+ .option('--last-name <name>', 'Last name')
36
+ .option('--display-name <name>', 'Display name')
37
+ .option('--username <name>', 'Username')
38
+ .action(async(options) => {
39
+ try {
40
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
41
+ const res = await createUser(controllerUrl, authConfig, {
42
+ email: requireNonEmpty(options.email, 'email'),
43
+ firstName: options.firstName,
44
+ lastName: options.lastName,
45
+ displayName: options.displayName,
46
+ username: options.username,
47
+ status: 'active'
48
+ });
49
+ throwIfApiFailed(res);
50
+ const row = unwrapControllerData(res);
51
+ logger.log(formatSuccessLine(`User created: ${row.id} (${row.email})`));
52
+ } catch (e) {
53
+ logger.error(formatBlockingError(e.message));
54
+ process.exit(1);
55
+ }
56
+ });
57
+ }
58
+
59
+ function registerUserList(userCmd) {
60
+ userCmd
61
+ .command('list')
62
+ .description('List users')
63
+ .option('--page <n>', 'Page', (v) => parseInt(v, 10))
64
+ .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
65
+ .option('--search <text>', 'Search users')
66
+ .option('--json', 'JSON output')
67
+ .action(async(options) => {
68
+ try {
69
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
70
+ const res = await listUsers(controllerUrl, authConfig, {
71
+ page: options.page,
72
+ pageSize: options.pageSize,
73
+ search: options.search
74
+ });
75
+ throwIfApiFailed(res);
76
+ const data = unwrapControllerData(res);
77
+ if (options.json) {
78
+ printJson(data);
79
+ return;
80
+ }
81
+ const items = Array.isArray(data) ? data : data?.data || [];
82
+ logger.log(chalk.bold(`\nUsers (${items.length} shown):\n`));
83
+ items.forEach((u) => logger.log(` ${u.id} ${u.email}`));
84
+ logger.log('');
85
+ } catch (e) {
86
+ logger.error(formatBlockingError(e.message));
87
+ process.exit(1);
88
+ }
89
+ });
90
+ }
91
+
92
+ function registerUserGet(userCmd) {
93
+ userCmd
94
+ .command('get <idOrEmail>')
95
+ .description('Get user by id or email')
96
+ .option('--json', 'JSON output')
97
+ .action(async(idOrEmail, options) => {
98
+ try {
99
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
100
+ const res = await getUser(controllerUrl, authConfig, requireNonEmpty(idOrEmail, 'idOrEmail'));
101
+ throwIfApiFailed(res);
102
+ const row = unwrapControllerData(res);
103
+ if (options.json) {
104
+ printJson(row);
105
+ return;
106
+ }
107
+ logger.log(headerKeyValue('Id:', row.id));
108
+ logger.log(headerKeyValue('Email:', row.email));
109
+ logger.log('');
110
+ } catch (e) {
111
+ logger.error(formatBlockingError(e.message));
112
+ process.exit(1);
113
+ }
114
+ });
115
+ }
116
+
117
+ function registerUserGroups(userCmd) {
118
+ userCmd
119
+ .command('groups <idOrEmail>')
120
+ .description('List groups for a user')
121
+ .option('--json', 'JSON output')
122
+ .action(async(idOrEmail, options) => {
123
+ try {
124
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
125
+ const res = await listUserGroups(
126
+ controllerUrl,
127
+ authConfig,
128
+ requireNonEmpty(idOrEmail, 'idOrEmail')
129
+ );
130
+ throwIfApiFailed(res);
131
+ const data = unwrapControllerData(res);
132
+ if (options.json) {
133
+ printJson(data);
134
+ return;
135
+ }
136
+ const items = Array.isArray(data) ? data : data?.data || [];
137
+ items.forEach((g) => logger.log(` ${g.id || g.groupId} ${g.name || ''}`));
138
+ logger.log('');
139
+ } catch (e) {
140
+ logger.error(formatBlockingError(e.message));
141
+ process.exit(1);
142
+ }
143
+ });
144
+ }
145
+
146
+ /**
147
+ * @param {import('commander').Command} userCmd
148
+ */
149
+ function setupIdentityUserCommands(userCmd) {
150
+ registerUserCreate(userCmd);
151
+ registerUserList(userCmd);
152
+ registerUserGet(userCmd);
153
+ registerUserGroups(userCmd);
154
+ }
155
+
156
+ module.exports = {
157
+ setupIdentityUserCommands
158
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Identity and RBAC commands (Controller users, groups, sync, cache).
3
+ *
4
+ * @fileoverview `aifabrix identity` command group
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { setupIdentityUserCommands } = require('./identity-user-cli');
12
+ const { setupIdentityGroupCommands } = require('./identity-group-cli');
13
+ const {
14
+ setupIdentityMembershipCommands,
15
+ setupIdentityRoleCommands,
16
+ setupIdentityCacheCommands
17
+ } = require('./identity-membership-role-cache');
18
+ const { setupIdentityApplyCommand, setupIdentitySyncCommand } = require('./identity-apply-sync');
19
+ const { IDENTITY_HELP_AFTER } = require('../cli/setup-app.help');
20
+
21
+ /**
22
+ * @param {import('commander').Command} program
23
+ */
24
+ function setupIdentityCommands(program) {
25
+ const identityCmd = program
26
+ .command('identity')
27
+ .description('Manage controller users, groups, RBAC mappings, cache, and dataplane identity sync')
28
+ .addHelpText('after', IDENTITY_HELP_AFTER);
29
+
30
+ setupIdentityUserCommands(identityCmd.command('user').description('User commands'));
31
+ setupIdentityGroupCommands(identityCmd.command('group').description('Group commands'));
32
+ setupIdentityMembershipCommands(
33
+ identityCmd.command('membership').description('User–group membership commands')
34
+ );
35
+ setupIdentityRoleCommands(identityCmd.command('role').description('Environment role commands'));
36
+ setupIdentityCacheCommands(identityCmd.command('cache').description('Controller cache commands'));
37
+ setupIdentityApplyCommand(identityCmd);
38
+ setupIdentitySyncCommand(identityCmd);
39
+ }
40
+
41
+ module.exports = {
42
+ setupIdentityCommands
43
+ };
@@ -17,6 +17,7 @@ const logger = require('../utils/logger');
17
17
  const { handleCredentialsLogin } = require('./login-credentials');
18
18
  const { handleDeviceCodeLogin } = require('./login-device');
19
19
  const { resolveControllerUrl } = require('../utils/controller-url');
20
+ const { tryRefreshDataplaneVersionAfterLogin } = require('./auth-status-dataplane-version');
20
21
 
21
22
  /**
22
23
  * Determine and validate authentication method
@@ -161,6 +162,47 @@ async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
161
162
  });
162
163
  }
163
164
 
165
+ /**
166
+ * @param {string} controllerUrl
167
+ * @param {Object} options
168
+ * @param {boolean} skipRefresh
169
+ * @returns {Promise<void>}
170
+ */
171
+ async function refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh) {
172
+ if (skipRefresh) return;
173
+ await tryRefreshDataplaneVersionAfterLogin(controllerUrl, options);
174
+ }
175
+
176
+ /**
177
+ * @returns {Promise<'credentials'|'device'>}
178
+ */
179
+ async function executeLoginMethod(method, controllerUrl, environment, options, skipRefresh) {
180
+ if (method === 'credentials') {
181
+ await handleCredentialsLoginFlow(controllerUrl, environment, options);
182
+ await refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh);
183
+ return 'credentials';
184
+ }
185
+ await handleDeviceCodeLoginFlow(controllerUrl, environment, options);
186
+ await refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh);
187
+ return 'device';
188
+ }
189
+
190
+ /**
191
+ * @param {Object} options
192
+ * @param {string} controllerUrl
193
+ * @param {string} environment
194
+ */
195
+ function logLoginSuccessFooter(options, controllerUrl, environment) {
196
+ if (options.compact) return;
197
+ logger.log(formatSuccessParagraph('Successfully logged in!'));
198
+ logger.log(chalk.gray(`Controller: ${controllerUrl}`));
199
+ logger.log(chalk.gray(`Environment: ${environment}`));
200
+ if (options.app) {
201
+ logger.log(chalk.gray(`App: ${options.app}`));
202
+ }
203
+ logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
204
+ }
205
+
164
206
  async function handleLogin(options) {
165
207
  if (!options.compact) {
166
208
  logger.log(chalk.blue('\n🔐 Logging in to Miso Controller...\n'));
@@ -169,25 +211,13 @@ async function handleLogin(options) {
169
211
  const controllerUrl = await normalizeControllerUrl(options);
170
212
  const environment = await handleEnvironmentConfig(options);
171
213
  const method = await determineAuthMethod(options.method);
172
-
173
214
  validateScopeOptions(method, options);
174
215
 
175
- if (method === 'credentials') {
176
- await handleCredentialsLoginFlow(controllerUrl, environment, options);
177
- } else if (method === 'device') {
178
- await handleDeviceCodeLoginFlow(controllerUrl, environment, options);
179
- return; // Early return for device flow (already saved config)
180
- }
216
+ const skipRefresh = options.skipDataplaneVersionRefresh === true;
217
+ const flow = await executeLoginMethod(method, controllerUrl, environment, options, skipRefresh);
218
+ if (flow === 'device') return;
181
219
 
182
- if (!options.compact) {
183
- logger.log(formatSuccessParagraph('Successfully logged in!'));
184
- logger.log(chalk.gray(`Controller: ${controllerUrl}`));
185
- logger.log(chalk.gray(`Environment: ${environment}`));
186
- if (options.app) {
187
- logger.log(chalk.gray(`App: ${options.app}`));
188
- }
189
- logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
190
- }
220
+ logLoginSuccessFooter(options, controllerUrl, environment);
191
221
  }
192
222
 
193
223
  module.exports = { handleLogin };