@aifabrix/builder 2.46.0 → 2.47.1

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 (191) hide show
  1. package/.npmrc.token +1 -0
  2. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  3. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  4. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  7. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  14. package/.temp/validation/summary.txt +5 -0
  15. package/jest.isolated-projects.js +18 -2
  16. package/jest.projects.js +24 -0
  17. package/lib/api/dataplane-health.api.js +19 -7
  18. package/lib/api/lifecycle.api.js +22 -1
  19. package/lib/api/resource-types.api.js +107 -0
  20. package/lib/api/runtime-configuration.api.js +72 -0
  21. package/lib/api/types/resource-types.types.js +45 -0
  22. package/lib/api/user-groups.api.js +9 -9
  23. package/lib/api/users.api.js +8 -8
  24. package/lib/api/wizard.api.js +10 -2
  25. package/lib/app/down.js +58 -26
  26. package/lib/app/index.js +2 -2
  27. package/lib/app/run-resolve-image.js +2 -1
  28. package/lib/app/run-stop-containers.js +113 -0
  29. package/lib/app/run.js +1 -31
  30. package/lib/app/show-display.js +35 -0
  31. package/lib/app/show.js +32 -1
  32. package/lib/build/index.js +17 -5
  33. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  34. package/lib/channels/add-channel.js +348 -0
  35. package/lib/channels/channel-artifact-generator.js +141 -0
  36. package/lib/channels/channel-audit.js +101 -0
  37. package/lib/channels/channel-auth-context.js +37 -0
  38. package/lib/channels/channel-credential.js +200 -0
  39. package/lib/channels/channel-display-name.js +64 -0
  40. package/lib/channels/channel-external-system.js +106 -0
  41. package/lib/channels/channel-integration-generator.js +274 -0
  42. package/lib/channels/channel-probe.js +89 -0
  43. package/lib/channels/channel-runtime-resolver.js +152 -0
  44. package/lib/channels/channel-status-display.js +141 -0
  45. package/lib/channels/channel-targets.js +115 -0
  46. package/lib/cli/index.js +18 -4
  47. package/lib/cli/infra-guided-auth.js +36 -12
  48. package/lib/cli/setup-app.js +54 -3
  49. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  50. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  51. package/lib/cli/setup-npm.js +59 -0
  52. package/lib/cli/setup-platform.js +1 -1
  53. package/lib/cli/setup-secrets.js +1 -1
  54. package/lib/cli/setup-utility-help-after.js +4 -1
  55. package/lib/cli/setup-utility-resolve.js +8 -5
  56. package/lib/commands/app-logs.js +54 -26
  57. package/lib/commands/app.js +41 -0
  58. package/lib/commands/auth-status-dataplane-version.js +97 -61
  59. package/lib/commands/auth-status.js +2 -7
  60. package/lib/commands/channel.js +58 -0
  61. package/lib/commands/datasource.js +1 -1
  62. package/lib/commands/identity-apply-sync.js +49 -8
  63. package/lib/commands/identity-group-cli.js +66 -26
  64. package/lib/commands/identity-membership-role-cache.js +58 -37
  65. package/lib/commands/identity-shared.js +22 -1
  66. package/lib/commands/identity-user-cli.js +80 -34
  67. package/lib/commands/login-device.js +11 -3
  68. package/lib/commands/login.js +8 -3
  69. package/lib/commands/npm-config.js +103 -0
  70. package/lib/commands/repair-auth-apply.js +30 -4
  71. package/lib/commands/repair-auth-config.js +8 -13
  72. package/lib/commands/repair-datasource-com-v2.js +15 -0
  73. package/lib/commands/repair-datasource-service-contract.js +206 -0
  74. package/lib/commands/repair-datasource.js +53 -18
  75. package/lib/commands/repair-env-template.js +12 -12
  76. package/lib/commands/repair-openapi-sync.js +26 -4
  77. package/lib/commands/repair-steps.js +28 -13
  78. package/lib/commands/repair-system-alignment.js +7 -4
  79. package/lib/commands/resource-type.js +371 -0
  80. package/lib/commands/setup-modes.js +1 -1
  81. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  82. package/lib/commands/test-trust-external.js +39 -90
  83. package/lib/commands/up-common.js +33 -1
  84. package/lib/commands/up-dataplane.js +8 -4
  85. package/lib/commands/upload.js +24 -3
  86. package/lib/commands/verify-trust-command-action.js +4 -1
  87. package/lib/commands/verify-trust-external.js +12 -20
  88. package/lib/core/config-setup-platform-mode.js +3 -3
  89. package/lib/core/setup-platform-mode.js +41 -6
  90. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  91. package/lib/datasource/capability/capability-manifest.js +254 -0
  92. package/lib/datasource/capability/capability-resolve.js +3 -4
  93. package/lib/datasource/capability/copy-operations.js +5 -3
  94. package/lib/datasource/capability/remove-operations.js +2 -1
  95. package/lib/datasource/capability/run-capability-copy.js +32 -4
  96. package/lib/datasource/capability/run-capability-diff.js +3 -3
  97. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  98. package/lib/datasource/capability/run-capability-edit.js +4 -7
  99. package/lib/datasource/capability/run-capability-relate.js +2 -4
  100. package/lib/datasource/capability/run-capability-remove.js +2 -4
  101. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  102. package/lib/datasource/datasource-identifier.js +2 -1
  103. package/lib/datasource/datasource-validate-display.js +1 -1
  104. package/lib/datasource/field-reference-validator.js +24 -3
  105. package/lib/datasource/orphan-profiles-validator.js +98 -0
  106. package/lib/datasource/validate.js +41 -23
  107. package/lib/external-system/download-helpers.js +87 -0
  108. package/lib/external-system/download.js +61 -122
  109. package/lib/external-system/generator.js +1 -59
  110. package/lib/external-system/test.js +10 -15
  111. package/lib/generator/builders.js +11 -1
  112. package/lib/generator/external-controller-manifest.js +5 -2
  113. package/lib/generator/split.js +3 -1
  114. package/lib/generator/wizard.js +1 -28
  115. package/lib/identity/fk-reference.js +65 -0
  116. package/lib/identity/identity-apply-core.js +13 -58
  117. package/lib/identity/identity-apply-phases.js +5 -1
  118. package/lib/identity/identity-display.js +243 -0
  119. package/lib/identity/identity-response-helpers.js +52 -0
  120. package/lib/identity/identity-sync-errors.js +62 -0
  121. package/lib/identity/resolve-user-id.js +129 -0
  122. package/lib/identity/username-default.js +31 -0
  123. package/lib/lifecycle/certification-progress.js +2 -2
  124. package/lib/lifecycle/report-display-trust.js +70 -0
  125. package/lib/lifecycle/report-display.js +14 -13
  126. package/lib/resolvers/datasource-resolver.js +2 -3
  127. package/lib/resolvers/resource-type-file.js +159 -0
  128. package/lib/schema/application-schema.json +16 -0
  129. package/lib/schema/deployment-rules.yaml +2 -1
  130. package/lib/schema/external-datasource.schema.json +372 -58
  131. package/lib/schema/external-system.schema.json +331 -2
  132. package/lib/schema/type/document-storage.json +9 -5
  133. package/lib/utils/api.js +2 -1
  134. package/lib/utils/app-run-containers.js +25 -2
  135. package/lib/utils/authentication-shape.js +224 -0
  136. package/lib/utils/build-copy.js +36 -5
  137. package/lib/utils/build-resolve-image.js +66 -12
  138. package/lib/utils/celery-companion-apps.js +53 -0
  139. package/lib/utils/cli-utils.js +4 -0
  140. package/lib/utils/compose-celery-worker.js +23 -0
  141. package/lib/utils/compose-generate-docker-compose.js +9 -1
  142. package/lib/utils/configuration-env-resolver.js +36 -0
  143. package/lib/utils/controller-url.js +4 -1
  144. package/lib/utils/credential-secrets-env-kv.js +216 -0
  145. package/lib/utils/credential-secrets-env.js +52 -262
  146. package/lib/utils/dataplane-resolver.js +4 -115
  147. package/lib/utils/declarative-url-ports.js +24 -1
  148. package/lib/utils/dev-platform-tls.js +148 -0
  149. package/lib/utils/docker-build.js +1 -4
  150. package/lib/utils/external-env-template.js +10 -3
  151. package/lib/utils/external-system-validators.js +8 -2
  152. package/lib/utils/health-check-request.js +114 -0
  153. package/lib/utils/health-check-url.js +3 -1
  154. package/lib/utils/health-check.js +1 -113
  155. package/lib/utils/help-builder.js +3 -0
  156. package/lib/utils/keycloak-realm-ready.js +5 -1
  157. package/lib/utils/localhost-public-host.js +31 -0
  158. package/lib/utils/npm-user-config.js +145 -0
  159. package/lib/utils/paths.js +25 -0
  160. package/lib/utils/platform-controller-url.js +58 -4
  161. package/lib/utils/platform-kv-ref.js +57 -0
  162. package/lib/utils/runtime-configuration-download.js +189 -0
  163. package/lib/utils/runtime-configuration-values.js +119 -0
  164. package/lib/utils/schema-loader.js +19 -4
  165. package/lib/utils/url-declarative-public-base.js +33 -25
  166. package/lib/utils/worker-sidecars.js +142 -0
  167. package/lib/validation/external-manifest-validator.js +2 -0
  168. package/lib/validation/runtime-configuration-rules.js +98 -0
  169. package/lib/validation/validate-display-dimensions.js +154 -0
  170. package/lib/validation/validate-display-sections.js +276 -0
  171. package/lib/validation/validate-display-steps.js +67 -0
  172. package/lib/validation/validate-display.js +24 -402
  173. package/package.json +1 -1
  174. package/templates/applications/dataplane/application.yaml +5 -0
  175. package/templates/applications/dataplane/env.template +26 -13
  176. package/templates/applications/dataplane/rbac.yaml +108 -1
  177. package/templates/applications/miso-controller/application.yaml +2 -2
  178. package/templates/applications/miso-controller/env.template +37 -122
  179. package/templates/applications/miso-controller/rbac.yaml +28 -13
  180. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  181. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  182. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  183. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  184. package/templates/channels/slack/install.template.md.hbs +49 -0
  185. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  186. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  187. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  188. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  189. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  190. package/templates/python/docker-compose.hbs +56 -0
  191. package/templates/typescript/docker-compose.hbs +52 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Channel CLI commands (plan 152).
3
+ *
4
+ * @fileoverview Scaffold communication external systems for channel deploy step 1
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('../utils/logger');
13
+ const { formatSuccessLine, formatBlockingError, sectionTitle, metadata } = require('../utils/cli-test-layout-chalk');
14
+ const { CHANNEL_TARGET_KEYS, isChannelTargetKey } = require('../channels/channel-targets');
15
+ const { scaffoldChannelIntegration } = require('../channels/channel-integration-generator');
16
+
17
+ /**
18
+ * @param {import('commander').Command} program
19
+ */
20
+ function setupChannelCommands(program) {
21
+ const channel = program
22
+ .command('channel')
23
+ .description('Assistant channel integration scaffolds (plan 152)');
24
+
25
+ channel
26
+ .command('scaffold <channelTarget>')
27
+ .description('Generate integration/<target>/ communication external system for deploy step 1')
28
+ .option('--force', 'Overwrite generated scaffold files when folder exists')
29
+ .option('--allow-experimental', 'Allow web-embed scaffold')
30
+ .action(async(channelTarget, options) => {
31
+ try {
32
+ if (!isChannelTargetKey(channelTarget)) {
33
+ throw new Error(
34
+ `Unknown channel target "${channelTarget}". Expected: ${CHANNEL_TARGET_KEYS.join(', ')}`
35
+ );
36
+ }
37
+ const result = scaffoldChannelIntegration(channelTarget, {
38
+ force: options.force === true,
39
+ allowExperimental: options.allowExperimental === true
40
+ });
41
+ logger.log(sectionTitle(`${channelTarget} channel scaffold`));
42
+ logger.log('');
43
+ logger.log(formatSuccessLine(`Created integration at ${result.integrationDir}`));
44
+ for (const file of result.files) {
45
+ logger.log(metadata(file));
46
+ }
47
+ logger.log('');
48
+ logger.log(chalk.white('Next steps:'));
49
+ logger.log(chalk.gray(` aifabrix deploy ${channelTarget}`));
50
+ logger.log(chalk.gray(` aifabrix app add-channel ${channelTarget} --name "Your Assistant"`));
51
+ } catch (error) {
52
+ logger.error(formatBlockingError(error.message || String(error)));
53
+ process.exit(1);
54
+ }
55
+ });
56
+ }
57
+
58
+ module.exports = { setupChannelCommands };
@@ -97,7 +97,7 @@ Omit [prefix] to list all datasources for the active environment.
97
97
 
98
98
  function setupDatasourceValidateCommand(datasource) {
99
99
  datasource.command('validate <file-or-key>')
100
- .description('Validate datasource JSON (file path or datasource key under integration/<app>/)')
100
+ .description('Validate datasource YAML/JSON (file path or datasource key under integration/<app>/)')
101
101
  .addHelpText('after', DATASOURCE_VALIDATE_HELP_AFTER)
102
102
  .action(async(fileOrKey) => {
103
103
  try {
@@ -7,14 +7,15 @@
7
7
  'use strict';
8
8
 
9
9
  const logger = require('../utils/logger');
10
- const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
10
+ const { formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
11
11
  const {
12
12
  resolveControllerAndAuth,
13
13
  resolveEnvKey,
14
14
  requireNonEmpty,
15
15
  unwrapControllerData,
16
16
  throwIfApiFailed,
17
- assertSyncStatsOk
17
+ assertSyncStatsOk,
18
+ logIdentityCliError
18
19
  } = require('./identity-shared');
19
20
  const { clearAuthCache } = require('../api/auth-cache.api');
20
21
  const { fullSyncToDataplane } = require('../api/dataplane-sync.api');
@@ -70,7 +71,7 @@ function setupIdentityApplyCommand(identityCmd) {
70
71
  );
71
72
  }
72
73
  } catch (e) {
73
- logger.error(formatBlockingError(e.message));
74
+ logIdentityCliError(e.message);
74
75
  process.exit(1);
75
76
  }
76
77
  });
@@ -103,6 +104,46 @@ async function maybePurgeCache(controllerUrl, authConfig, purgeCache) {
103
104
  logger.log(formatSuccessLine('Controller cache cleared'));
104
105
  }
105
106
 
107
+ /**
108
+ * @param {string} controllerUrl
109
+ * @param {string} envKey
110
+ * @param {Object} authConfig
111
+ * @returns {Promise<string>}
112
+ */
113
+ async function resolveSyncDataplaneUrl(controllerUrl, envKey, authConfig) {
114
+ try {
115
+ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
116
+ return await resolveDataplaneUrl(controllerUrl, envKey, authConfig, { silent: true });
117
+ } catch {
118
+ return '—';
119
+ }
120
+ }
121
+
122
+ /**
123
+ * @param {Object} params
124
+ * @param {string} params.controllerUrl
125
+ * @param {string} params.envKey
126
+ * @param {Object} params.authConfig
127
+ * @param {Object|null|undefined} params.stats
128
+ */
129
+ async function printIdentitySyncSuccess({ controllerUrl, envKey, authConfig, stats }) {
130
+ const dataplaneUrl = await resolveSyncDataplaneUrl(controllerUrl, envKey, authConfig);
131
+ const usersProcessed = Number(stats?.usersProcessed ?? 0);
132
+ const groupsProcessed = Number(stats?.groupsProcessed ?? 0);
133
+ const syncErrors = Number(stats?.errors ?? 0);
134
+
135
+ logger.log('');
136
+ logger.log(formatSuccessLine(`Identity synced to dataplane (${envKey})`));
137
+ logger.log(headerKeyValue(' Controller:', controllerUrl));
138
+ logger.log(headerKeyValue(' Dataplane:', dataplaneUrl));
139
+ logger.log('');
140
+ logger.log(` Users: ${usersProcessed} processed`);
141
+ logger.log(` Groups: ${groupsProcessed} processed`);
142
+ if (syncErrors > 0) {
143
+ logger.log(` Errors: ${syncErrors}`);
144
+ }
145
+ }
146
+
106
147
  async function runIdentitySyncAction(options) {
107
148
  try {
108
149
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
@@ -116,16 +157,16 @@ async function runIdentitySyncAction(options) {
116
157
  printJson(stats);
117
158
  return;
118
159
  }
119
- logger.log(formatSuccessLine(`Identity synced to dataplane (${envKey})`));
120
- logger.log(` usersProcessed=${stats?.usersProcessed ?? 0}`);
121
- logger.log(` groupsProcessed=${stats?.groupsProcessed ?? 0}`);
160
+ await printIdentitySyncSuccess({ controllerUrl, envKey, authConfig, stats });
122
161
  } catch (e) {
123
- logger.error(formatBlockingError(e.message));
162
+ logIdentityCliError(e.message);
124
163
  process.exit(1);
125
164
  }
126
165
  }
127
166
 
128
167
  module.exports = {
129
168
  setupIdentityApplyCommand,
130
- setupIdentitySyncCommand
169
+ setupIdentitySyncCommand,
170
+ printIdentitySyncSuccess,
171
+ resolveSyncDataplaneUrl
131
172
  };
@@ -7,7 +7,7 @@
7
7
  'use strict';
8
8
 
9
9
  const logger = require('../utils/logger');
10
- const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
10
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
11
11
  const {
12
12
  resolveControllerAndAuth,
13
13
  requireNonEmpty,
@@ -15,11 +15,26 @@ const {
15
15
  throwIfApiFailed
16
16
  } = require('./identity-shared');
17
17
  const { listGroups, getGroup, createGroup, listGroupMembers } = require('../api/groups.api');
18
+ const {
19
+ extractPaginatedList,
20
+ printGroupMembersTable,
21
+ printGroupRecord,
22
+ printIdentityEmpty,
23
+ printIdentityListHeader,
24
+ printIdentityNotFound,
25
+ printIdentityTable,
26
+ printListPaginationFooter,
27
+ GROUP_LIST_COLUMNS
28
+ } = require('../identity/identity-display');
18
29
 
19
30
  function printJson(data) {
20
31
  logger.log(JSON.stringify(data, null, 2));
21
32
  }
22
33
 
34
+ function groupNotFoundHints() {
35
+ return ['af identity group list --search <text>', 'Use group name or id from group list'];
36
+ }
37
+
23
38
  function registerGroupCreate(groupCmd) {
24
39
  groupCmd
25
40
  .command('create')
@@ -51,7 +66,7 @@ function registerGroupList(groupCmd) {
51
66
  .description('List groups')
52
67
  .option('--page <n>', 'Page', (v) => parseInt(v, 10))
53
68
  .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
54
- .option('--search <text>', 'Search groups')
69
+ .option('--search <text>', 'Search groups by name or display name')
55
70
  .option('--json', 'JSON output')
56
71
  .action(async(options) => {
57
72
  try {
@@ -62,14 +77,24 @@ function registerGroupList(groupCmd) {
62
77
  search: options.search
63
78
  });
64
79
  throwIfApiFailed(res);
65
- const data = unwrapControllerData(res);
66
80
  if (options.json) {
67
- printJson(data);
81
+ printJson(res.data ?? res);
68
82
  return;
69
83
  }
70
- const items = Array.isArray(data) ? data : data?.data || [];
71
- items.forEach((g) => logger.log(` ${g.id} ${g.name}`));
72
- logger.log('');
84
+ const { items, meta } = extractPaginatedList(res);
85
+ printIdentityListHeader({
86
+ emoji: '👥',
87
+ title: 'Groups',
88
+ context: controllerUrl,
89
+ shown: items.length,
90
+ total: meta?.totalItems ?? items.length
91
+ });
92
+ if (items.length === 0) {
93
+ printIdentityEmpty('No groups found.', ['Try --search']);
94
+ return;
95
+ }
96
+ printIdentityTable(GROUP_LIST_COLUMNS, items);
97
+ printListPaginationFooter(meta);
73
98
  } catch (e) {
74
99
  logger.error(formatBlockingError(e.message));
75
100
  process.exit(1);
@@ -79,22 +104,28 @@ function registerGroupList(groupCmd) {
79
104
 
80
105
  function registerGroupGet(groupCmd) {
81
106
  groupCmd
82
- .command('get <idOrName>')
107
+ .command('get <groupIdOrName>')
83
108
  .description('Get group by id or name')
84
109
  .option('--json', 'JSON output')
85
- .action(async(idOrName, options) => {
110
+ .action(async(groupIdOrName, options) => {
86
111
  try {
87
112
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
88
- const res = await getGroup(controllerUrl, authConfig, requireNonEmpty(idOrName, 'idOrName'));
89
- throwIfApiFailed(res);
113
+ const value = requireNonEmpty(groupIdOrName, 'groupIdOrName');
114
+ const res = await getGroup(controllerUrl, authConfig, value);
115
+ if (!res || res.success === false) {
116
+ printIdentityNotFound('Group', value, groupNotFoundHints());
117
+ process.exit(1);
118
+ }
90
119
  const row = unwrapControllerData(res);
120
+ if (!row?.id) {
121
+ printIdentityNotFound('Group', value, groupNotFoundHints());
122
+ process.exit(1);
123
+ }
91
124
  if (options.json) {
92
125
  printJson(row);
93
126
  return;
94
127
  }
95
- logger.log(headerKeyValue('Id:', row.id));
96
- logger.log(headerKeyValue('Name:', row.name));
97
- logger.log('');
128
+ printGroupRecord(row);
98
129
  } catch (e) {
99
130
  logger.error(formatBlockingError(e.message));
100
131
  process.exit(1);
@@ -104,26 +135,35 @@ function registerGroupGet(groupCmd) {
104
135
 
105
136
  function registerGroupMembers(groupCmd) {
106
137
  groupCmd
107
- .command('members <idOrName>')
138
+ .command('members <groupIdOrName>')
108
139
  .description('List members of a group')
109
140
  .option('--json', 'JSON output')
110
- .action(async(idOrName, options) => {
141
+ .action(async(groupIdOrName, options) => {
111
142
  try {
112
143
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
113
- const res = await listGroupMembers(
114
- controllerUrl,
115
- authConfig,
116
- requireNonEmpty(idOrName, 'idOrName')
117
- );
118
- throwIfApiFailed(res);
144
+ const value = requireNonEmpty(groupIdOrName, 'groupIdOrName');
145
+ const res = await listGroupMembers(controllerUrl, authConfig, value);
146
+ if (!res || res.success === false) {
147
+ printIdentityNotFound('Group', value, groupNotFoundHints());
148
+ process.exit(1);
149
+ }
119
150
  const data = unwrapControllerData(res);
151
+ const items = Array.isArray(data) ? data : data?.data || [];
120
152
  if (options.json) {
121
- printJson(data);
153
+ printJson(items);
122
154
  return;
123
155
  }
124
- const items = Array.isArray(data) ? data : data?.data || [];
125
- items.forEach((m) => logger.log(` ${m.userId || m.id} ${m.email || ''}`));
126
- logger.log('');
156
+ printIdentityListHeader({
157
+ emoji: '👤',
158
+ title: `Members of ${value}`,
159
+ shown: items.length,
160
+ total: items.length
161
+ });
162
+ if (items.length === 0) {
163
+ printIdentityEmpty('No members in this group.');
164
+ return;
165
+ }
166
+ printGroupMembersTable(items);
127
167
  } catch (e) {
128
168
  logger.error(formatBlockingError(e.message));
129
169
  process.exit(1);
@@ -19,30 +19,43 @@ const {
19
19
  const { addUserToGroup, removeUserFromGroup } = require('../api/user-groups.api');
20
20
  const { listEnvironmentRoles, updateRoleGroups } = require('../api/environments.api');
21
21
  const { clearAuthCache, invalidateAuthCache } = require('../api/auth-cache.api');
22
+ const {
23
+ printIdentityEmpty,
24
+ printIdentityListHeader,
25
+ printIdentityTable
26
+ } = require('../identity/identity-display');
22
27
 
23
28
  function printJson(data) {
24
29
  logger.log(JSON.stringify(data, null, 2));
25
30
  }
26
31
 
32
+ const ROLE_LIST_COLUMNS = [
33
+ { label: 'Role', width: 36, value: (r) => r.value || r.role || r.name || '—' },
34
+ {
35
+ label: 'Groups',
36
+ width: 44,
37
+ value: (r) => {
38
+ const groups = Array.isArray(r.groups) ? r.groups.filter(Boolean) : [];
39
+ return groups.length > 0 ? groups.join(', ') : '—';
40
+ }
41
+ }
42
+ ];
43
+
27
44
  /**
28
45
  * @param {import('commander').Command} memCmd
29
46
  */
30
47
  function setupIdentityMembershipCommands(memCmd) {
31
48
  memCmd
32
- .command('add <userId> <groupId>')
33
- .description('Add user to group (controller ids)')
34
- .action(async(userId, groupId) => {
49
+ .command('add <userIdOrEmail> <groupIdOrName>')
50
+ .description('Add user to group (id, email, username + group id or name)')
51
+ .action(async(userIdOrEmail, groupIdOrName) => {
35
52
  try {
36
53
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
37
- const res = await addUserToGroup(
38
- controllerUrl,
39
- authConfig,
40
- requireNonEmpty(userId, 'userId'),
41
- requireNonEmpty(groupId, 'groupId'),
42
- {}
43
- );
54
+ const userRef = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
55
+ const groupRef = requireNonEmpty(groupIdOrName, 'groupIdOrName');
56
+ const res = await addUserToGroup(controllerUrl, authConfig, userRef, groupRef, {});
44
57
  throwIfApiFailed(res);
45
- logger.log(formatSuccessLine(`Membership created: ${userId} → ${groupId}`));
58
+ logger.log(formatSuccessLine(`Membership created: ${userRef} → ${groupRef}`));
46
59
  } catch (e) {
47
60
  logger.error(formatBlockingError(e.message));
48
61
  process.exit(1);
@@ -50,19 +63,16 @@ function setupIdentityMembershipCommands(memCmd) {
50
63
  });
51
64
 
52
65
  memCmd
53
- .command('remove <userId> <groupId>')
66
+ .command('remove <userIdOrEmail> <groupIdOrName>')
54
67
  .description('Remove user from group')
55
- .action(async(userId, groupId) => {
68
+ .action(async(userIdOrEmail, groupIdOrName) => {
56
69
  try {
57
70
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
58
- const res = await removeUserFromGroup(
59
- controllerUrl,
60
- authConfig,
61
- requireNonEmpty(userId, 'userId'),
62
- requireNonEmpty(groupId, 'groupId')
63
- );
71
+ const userRef = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
72
+ const groupRef = requireNonEmpty(groupIdOrName, 'groupIdOrName');
73
+ const res = await removeUserFromGroup(controllerUrl, authConfig, userRef, groupRef);
64
74
  throwIfApiFailed(res);
65
- logger.log(formatSuccessLine(`Membership removed: ${userId} → ${groupId}`));
75
+ logger.log(formatSuccessLine(`Membership removed: ${userRef} → ${groupRef}`));
66
76
  } catch (e) {
67
77
  logger.error(formatBlockingError(e.message));
68
78
  process.exit(1);
@@ -73,29 +83,40 @@ function setupIdentityMembershipCommands(memCmd) {
73
83
  /**
74
84
  * @param {import('commander').Command} roleCmd
75
85
  */
86
+ async function handleRoleListAction(options) {
87
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
88
+ const envKey = await resolveEnvKey(options);
89
+ const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
90
+ throwIfApiFailed(res);
91
+ const data = unwrapControllerData(res);
92
+ if (options.json) {
93
+ printJson(data);
94
+ return;
95
+ }
96
+ const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
97
+ printIdentityListHeader({
98
+ emoji: '🔐',
99
+ title: 'Role → group mappings',
100
+ context: `environment: ${envKey}`,
101
+ shown: (items || []).length,
102
+ total: (items || []).length
103
+ });
104
+ if (!items || items.length === 0) {
105
+ printIdentityEmpty('No roles found for this environment.');
106
+ return;
107
+ }
108
+ printIdentityTable(ROLE_LIST_COLUMNS, items);
109
+ }
110
+
76
111
  function setupIdentityRoleCommands(roleCmd) {
77
112
  roleCmd
78
113
  .command('list')
79
114
  .description('List roles and group mappings for an environment')
80
- .requiredOption('-e, --env <key>', 'Environment key (dev, tst, pro)')
115
+ .option('-e, --env <key>', 'Environment key (defaults from aifabrix config)')
81
116
  .option('--json', 'JSON output')
82
117
  .action(async(options) => {
83
118
  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('');
119
+ await handleRoleListAction(options);
99
120
  } catch (e) {
100
121
  logger.error(formatBlockingError(e.message));
101
122
  process.exit(1);
@@ -105,7 +126,7 @@ function setupIdentityRoleCommands(roleCmd) {
105
126
  roleCmd
106
127
  .command('set-groups <roleValue>')
107
128
  .description('Map role to groups in an environment')
108
- .requiredOption('-e, --env <key>', 'Environment key')
129
+ .option('-e, --env <key>', 'Environment key (defaults from aifabrix config)')
109
130
  .requiredOption('--groups <names>', 'Comma-separated group names')
110
131
  .action(async(roleValue, options) => {
111
132
  try {
@@ -6,6 +6,9 @@
6
6
 
7
7
  'use strict';
8
8
 
9
+ const chalk = require('chalk');
10
+ const logger = require('../utils/logger');
11
+ const { formatBlockingError } = require('../utils/cli-layout-chalk');
9
12
  const { resolveControllerUrl } = require('../utils/controller-url');
10
13
  const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
11
14
  const { getOrRefreshDeviceToken } = require('../utils/token-manager');
@@ -82,6 +85,23 @@ function parseGroupsList(groupsArg) {
82
85
  return parts;
83
86
  }
84
87
 
88
+ /**
89
+ * Print a blocking CLI error with optional multi-line guidance in gray.
90
+ *
91
+ * @param {string} message
92
+ * @returns {void}
93
+ */
94
+ function logIdentityCliError(message) {
95
+ const lines = String(message || '').split('\n');
96
+ const head = lines[0] || 'Request failed';
97
+ logger.error(formatBlockingError(head));
98
+ lines.slice(1).forEach((line) => {
99
+ if (line.trim()) {
100
+ logger.log(chalk.gray(line));
101
+ }
102
+ });
103
+ }
104
+
85
105
  module.exports = {
86
106
  resolveControllerAndAuth,
87
107
  resolveEnvKey,
@@ -90,5 +110,6 @@ module.exports = {
90
110
  unwrapControllerData,
91
111
  throwIfApiFailed,
92
112
  assertSyncStatsOk,
93
- apiErrorMessage
113
+ apiErrorMessage,
114
+ logIdentityCliError
94
115
  };