@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,89 @@
1
+ /**
2
+ * Channel install probe checks (plan 152).
3
+ *
4
+ * @fileoverview Callback, assistant catalog, and inbound verification probes
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { ApiClient } = require('../api/index');
12
+ const { fetchDataplaneGeneralHealth } = require('../api/dataplane-health.api');
13
+
14
+ /**
15
+ * @async
16
+ * @param {string} url
17
+ * @returns {Promise<boolean>}
18
+ */
19
+ async function isUrlReachable(url) {
20
+ if (!url) return false;
21
+ const client = new ApiClient(url.split('/api/')[0] || url);
22
+ try {
23
+ const pathPart = url.includes('/api/') ? url.substring(url.indexOf('/api/')) : '/health';
24
+ const res = await client.get(pathPart, { timeoutMs: 8000 });
25
+ return res && res.success !== false;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @async
33
+ * @param {string} dataplaneUrl
34
+ * @param {Object} authConfig
35
+ * @returns {Promise<boolean>}
36
+ */
37
+ async function isAssistantCatalogReachable(dataplaneUrl, authConfig) {
38
+ const client = require('../api/index').createDataplaneApiClient(dataplaneUrl, authConfig);
39
+ try {
40
+ const res = await client.get('/api/v1/workers/available', { timeoutMs: 10000 });
41
+ return res && res.success !== false;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * @param {Object|null} channelExt
49
+ * @returns {Object}
50
+ */
51
+ function evaluateInboundVerification(channelExt) {
52
+ const credentialKey = channelExt?.credentialKey || '';
53
+ return {
54
+ slackSigningSecretConfigured: credentialKey.includes('slack'),
55
+ teamsBotOidcValidationConfigured: credentialKey.includes('teams-bot'),
56
+ issuerAudienceConfigured: Boolean(channelExt?.type),
57
+ replayProtectionConfigured: Boolean(channelExt?.status === 'installed' || channelExt?.status === 'validated')
58
+ };
59
+ }
60
+
61
+ /**
62
+ * @async
63
+ * @param {Object} ctx
64
+ * @param {Object|null} channelExt
65
+ * @returns {Promise<Object>}
66
+ */
67
+ async function runChannelProbe(ctx, channelExt) {
68
+ const health = await fetchDataplaneGeneralHealth(ctx.dataplaneUrl, { timeoutMs: 8000 });
69
+ const catalogOk = await isAssistantCatalogReachable(ctx.dataplaneUrl, ctx.authConfig);
70
+ const callbackOk = health?.status === 'healthy' || health?.status === 'ok';
71
+
72
+ const inbound = evaluateInboundVerification(channelExt);
73
+
74
+ return {
75
+ dataplaneHealth: health?.status || 'unknown',
76
+ callbackReachable: callbackOk,
77
+ assistantCatalogReachable: catalogOk,
78
+ credentialValid: Boolean(channelExt?.credentialKey),
79
+ inboundVerification: inbound,
80
+ passed: callbackOk && catalogOk && Boolean(channelExt?.credentialKey)
81
+ };
82
+ }
83
+
84
+ module.exports = {
85
+ isUrlReachable,
86
+ isAssistantCatalogReachable,
87
+ evaluateInboundVerification,
88
+ runChannelProbe
89
+ };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Resolve runtime URLs for channel templates (no hardcoded env URLs).
3
+ *
4
+ * @fileoverview Channel runtime value resolution (plan 152)
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getChannelTarget } = require('./channel-targets');
12
+
13
+ /**
14
+ * Normalize dataplane base URL (no trailing slash).
15
+ * @param {string} dataplaneBaseUrl
16
+ * @returns {string}
17
+ */
18
+ function normalizeBaseUrl(dataplaneBaseUrl) {
19
+ if (!dataplaneBaseUrl || typeof dataplaneBaseUrl !== 'string') {
20
+ throw new Error('dataplaneBaseUrl is required');
21
+ }
22
+ return dataplaneBaseUrl.trim().replace(/\/+$/, '');
23
+ }
24
+
25
+ /**
26
+ * @param {string} base
27
+ * @param {string} path
28
+ * @returns {string}
29
+ */
30
+ function joinUrl(base, path) {
31
+ return `${normalizeBaseUrl(base)}${path.startsWith('/') ? path : `/${path}`}`;
32
+ }
33
+
34
+ /**
35
+ * Dataplane external-system route prefix for webhook URLs.
36
+ * @param {string} routeSystemKey
37
+ * @returns {string}
38
+ */
39
+ function externalSystemsPrefix(routeSystemKey) {
40
+ return `/api/v1/external/systems/${routeSystemKey}`;
41
+ }
42
+
43
+ /**
44
+ * Relative endpoint paths per channel target (under external/systems/{routeSystemKey}).
45
+ * @param {string} channelTarget
46
+ * @returns {Record<string, string>}
47
+ */
48
+ function getEndpointPathSuffixes(channelTarget) {
49
+ const maps = {
50
+ slack: {
51
+ eventsUrl: '/events',
52
+ interactionUrl: '/interactive',
53
+ oauthCallbackUrl: '/oauth/callback',
54
+ commandUrl: '/commands'
55
+ },
56
+ 'teams-bot': {
57
+ messagesUrl: '/messages',
58
+ notificationsUrl: '/notifications',
59
+ botConfigUrl: '/bot-config'
60
+ },
61
+ 'microsoft-copilot': {
62
+ openapiUrl: '/openapi.yaml',
63
+ actionsBaseUrl: '/actions'
64
+ },
65
+ chatgpt: {
66
+ openapiUrl: '/openapi.yaml',
67
+ mcpUrl: '/mcp',
68
+ actionsBaseUrl: '/actions'
69
+ },
70
+ 'web-embed': {
71
+ configUrl: '/config'
72
+ }
73
+ };
74
+ return maps[channelTarget] || {};
75
+ }
76
+
77
+ /**
78
+ * Legacy / non-external-system paths (Copilot/ChatGPT until dataplane routes exist).
79
+ * @param {string} channelTarget
80
+ * @returns {Record<string, string>}
81
+ */
82
+ function getLegacyApiPaths(channelTarget) {
83
+ const legacy = {
84
+ 'microsoft-copilot': {
85
+ openapiUrl: '/api/v1/copilot/openapi.yaml',
86
+ actionsBaseUrl: '/api/v1/copilot/actions'
87
+ },
88
+ chatgpt: {
89
+ openapiUrl: '/api/v1/chatgpt/openapi.yaml',
90
+ mcpUrl: '/api/v1/chatgpt/mcp',
91
+ actionsBaseUrl: '/api/v1/chatgpt/actions'
92
+ },
93
+ 'web-embed': {
94
+ configUrl: '/api/v1/web-embed/config'
95
+ }
96
+ };
97
+ return legacy[channelTarget] || {};
98
+ }
99
+
100
+ /**
101
+ * @param {Object} params
102
+ * @param {string} params.dataplaneBaseUrl
103
+ * @param {string} params.channelTarget
104
+ * @param {string} params.appName
105
+ * @param {string} [params.baseName]
106
+ * @param {string} [params.environment]
107
+ * @param {string} params.appDescription
108
+ * @param {string} [params.icon]
109
+ * @returns {Object}
110
+ */
111
+ function resolveChannelRuntimeValues(params) {
112
+ const target = getChannelTarget(params.channelTarget);
113
+ const base = normalizeBaseUrl(params.dataplaneBaseUrl);
114
+ const routeKey = target.routeSystemKey || target.key;
115
+ const systemPrefix = externalSystemsPrefix(routeKey);
116
+ const suffixes = getEndpointPathSuffixes(params.channelTarget);
117
+ const legacy = getLegacyApiPaths(params.channelTarget);
118
+
119
+ const resolved = {
120
+ dataplaneBaseUrl: base,
121
+ channelTarget: params.channelTarget,
122
+ routeSystemKey: routeKey,
123
+ appName: params.appName,
124
+ baseName: params.baseName || params.appName,
125
+ environment: params.environment || 'dev',
126
+ appDescription: params.appDescription,
127
+ icon: params.icon || '',
128
+ credentialKey: target.credentialKey,
129
+ assistantCatalogUrl: joinUrl(base, '/api/v1/workers/available'),
130
+ scopes: []
131
+ };
132
+
133
+ for (const [key, suffix] of Object.entries(suffixes)) {
134
+ resolved[key] = joinUrl(base, `${systemPrefix}${suffix}`);
135
+ }
136
+
137
+ for (const [key, legacyPath] of Object.entries(legacy)) {
138
+ if (!resolved[key]) {
139
+ resolved[key] = joinUrl(base, legacyPath);
140
+ }
141
+ }
142
+
143
+ return resolved;
144
+ }
145
+
146
+ module.exports = {
147
+ normalizeBaseUrl,
148
+ joinUrl,
149
+ externalSystemsPrefix,
150
+ getEndpointPathSuffixes,
151
+ resolveChannelRuntimeValues
152
+ };
@@ -0,0 +1,141 @@
1
+ /**
2
+ * TTY output for app add-channel (plan 152).
3
+ *
4
+ * @fileoverview Channel install status display — customer language: Assistant
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 {
14
+ formatSuccessLine,
15
+ formatWarningLine,
16
+ formatBlockingError,
17
+ sectionTitle,
18
+ metadata
19
+ } = require('../utils/cli-test-layout-chalk');
20
+
21
+ /** @returns {string} */
22
+ function separatorLine() {
23
+ const gray = chalk.gray;
24
+ return typeof gray === 'function'
25
+ ? gray('────────────────────────────────────────')
26
+ : '────────────────────────────────────────';
27
+ }
28
+
29
+ /**
30
+ * @param {Object} params
31
+ * @param {string} params.channelTarget
32
+ * @param {string} params.name
33
+ * @param {Object|null} params.channelExt
34
+ * @param {string} [params.approvalUrl]
35
+ */
36
+ function displayExistingChannelStatus(params) {
37
+ logger.log(sectionTitle(`${params.channelTarget} channel: ${params.name}`));
38
+ logger.log('');
39
+ logger.log(metadata(`Status: ${params.channelExt?.status || 'unknown'}`));
40
+ if (params.channelExt?.baseName && params.channelExt.baseName !== params.name) {
41
+ logger.log(metadata(`Base name: ${params.channelExt.baseName}`));
42
+ }
43
+ logger.log(metadata(`External System: ${params.channelTarget}`));
44
+ logger.log(metadata(`Credential: ${params.channelExt?.credentialKey || '—'}`));
45
+ if (params.channelExt?.capabilities?.length) {
46
+ logger.log(metadata('Capabilities:'));
47
+ for (const cap of params.channelExt.capabilities) {
48
+ logger.log(` - ${cap}`);
49
+ }
50
+ }
51
+ logger.log('');
52
+ logger.log(formatWarningLine('Channel install already configured. Use --force to regenerate artifacts.'));
53
+ if (params.approvalUrl) {
54
+ logger.log(metadata(`Approval URL: ${params.approvalUrl}`));
55
+ }
56
+ logger.log(separatorLine());
57
+ }
58
+
59
+ /**
60
+ * @param {Object} result
61
+ */
62
+ function displayChannelCoreFields(result) {
63
+ logger.log(sectionTitle(`${result.channelTarget} channel: ${result.name}`));
64
+ logger.log('');
65
+ logger.log(metadata(`Status: ${result.statusLabel}`));
66
+ if (result.baseName && result.baseName !== result.name) {
67
+ logger.log(metadata(`Base name: ${result.baseName}`));
68
+ }
69
+ logger.log(metadata(`External System: ${result.channelTarget}`));
70
+ logger.log(metadata(`Credential: ${result.credentialKey}`));
71
+ if (result.capabilities?.length) {
72
+ logger.log(metadata('Capabilities:'));
73
+ for (const cap of result.capabilities) {
74
+ logger.log(` - ${cap}`);
75
+ }
76
+ }
77
+ if (result.artifactPath) {
78
+ logger.log(metadata(`Artifact: ${result.artifactPath}`));
79
+ }
80
+ if (result.installGuidePath) {
81
+ logger.log(metadata(`Install guide: ${result.installGuidePath}`));
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @param {Object|null} validation
87
+ */
88
+ function displayValidationSection(validation) {
89
+ if (!validation) return;
90
+ logger.log('');
91
+ logger.log(metadata('Validation:'));
92
+ if (validation.callbackReachable) {
93
+ logger.log(formatSuccessLine('Callback reachable'));
94
+ }
95
+ if (validation.credentialValid) {
96
+ logger.log(formatSuccessLine('Credential valid'));
97
+ }
98
+ if (validation.assistantCatalogReachable) {
99
+ logger.log(formatSuccessLine('Assistant catalog reachable'));
100
+ }
101
+ if (validation.inboundVerification) {
102
+ logger.log(formatSuccessLine('Inbound verification configured'));
103
+ }
104
+ }
105
+
106
+ /**
107
+ * @param {Object} result
108
+ */
109
+ function displayApprovalSection(result) {
110
+ if (!result.approvalUrl) return;
111
+ logger.log('');
112
+ logger.log(formatWarningLine('Approval required'));
113
+ logger.log(metadata('Open this URL as platform admin:'));
114
+ logger.log(chalk.cyan(result.approvalUrl));
115
+ logger.log('');
116
+ logger.log(metadata('After approval, run:'));
117
+ logger.log(chalk.white(` aifabrix app add-channel ${result.channelTarget} --name "${result.name}"`));
118
+ }
119
+
120
+ /**
121
+ * @param {Object} result
122
+ */
123
+ function displayChannelInstallResult(result) {
124
+ displayChannelCoreFields(result);
125
+ displayValidationSection(result.validation);
126
+ displayApprovalSection(result);
127
+ logger.log(separatorLine());
128
+ }
129
+
130
+ /**
131
+ * @param {string} message
132
+ */
133
+ function displayChannelError(message) {
134
+ logger.error(formatBlockingError(message));
135
+ }
136
+
137
+ module.exports = {
138
+ displayExistingChannelStatus,
139
+ displayChannelInstallResult,
140
+ displayChannelError
141
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Channel target registry for assistant communication channels (plan 152).
3
+ *
4
+ * @fileoverview Channel install targets — templates, capabilities, approval semantics
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /** @typedef {'teams-bot'|'slack'|'microsoft-copilot'|'chatgpt'|'web-embed'} ChannelTargetKey */
12
+
13
+ const DEFAULT_CAPABILITIES = ['chat', 'notify'];
14
+
15
+ const CHANNEL_TARGETS = {
16
+ 'teams-bot': {
17
+ key: 'teams-bot',
18
+ channelType: 'teamsBot',
19
+ templateDir: 'teams-bot',
20
+ artifactFileName: 'channel-teams-bot.json',
21
+ artifactFormat: 'json',
22
+ credentialKey: 'teams-bot',
23
+ routeSystemKey: 'teams',
24
+ capabilities: DEFAULT_CAPABILITIES,
25
+ approvalRequired: true,
26
+ experimental: false
27
+ },
28
+ slack: {
29
+ key: 'slack',
30
+ channelType: 'slack',
31
+ templateDir: 'slack',
32
+ artifactFileName: 'channel-slack.json',
33
+ artifactFormat: 'json',
34
+ credentialKey: 'slack-app',
35
+ routeSystemKey: 'slack',
36
+ capabilities: DEFAULT_CAPABILITIES,
37
+ approvalRequired: true,
38
+ experimental: false
39
+ },
40
+ 'microsoft-copilot': {
41
+ key: 'microsoft-copilot',
42
+ channelType: 'microsoftCopilot',
43
+ templateDir: 'microsoft-copilot',
44
+ artifactFileName: 'channel-microsoft-copilot.json',
45
+ artifactFormat: 'json',
46
+ credentialKey: 'microsoft-copilot-channel',
47
+ capabilities: [...DEFAULT_CAPABILITIES, 'enterpriseActions'],
48
+ approvalRequired: true,
49
+ experimental: false
50
+ },
51
+ chatgpt: {
52
+ key: 'chatgpt',
53
+ channelType: 'chatgpt',
54
+ templateDir: 'chatgpt',
55
+ artifactFileName: 'channel-chatgpt.json',
56
+ artifactFormat: 'json',
57
+ credentialKey: 'chatgpt-channel',
58
+ capabilities: ['chat', 'enterpriseActions'],
59
+ approvalRequired: true,
60
+ experimental: false
61
+ },
62
+ 'web-embed': {
63
+ key: 'web-embed',
64
+ channelType: 'webEmbed',
65
+ templateDir: 'web-embed',
66
+ artifactFileName: 'channel-web-embed.json',
67
+ artifactFormat: 'json',
68
+ credentialKey: 'web-embed-channel',
69
+ capabilities: DEFAULT_CAPABILITIES,
70
+ approvalRequired: false,
71
+ experimental: true
72
+ }
73
+ };
74
+
75
+ const CHANNEL_TARGET_KEYS = Object.keys(CHANNEL_TARGETS);
76
+
77
+ /**
78
+ * @param {string} channelTarget
79
+ * @returns {boolean}
80
+ */
81
+ function isChannelTargetKey(channelTarget) {
82
+ return typeof channelTarget === 'string' && CHANNEL_TARGET_KEYS.includes(channelTarget);
83
+ }
84
+
85
+ /**
86
+ * @param {string} channelTarget
87
+ * @returns {typeof CHANNEL_TARGETS[string]}
88
+ */
89
+ function getChannelTarget(channelTarget) {
90
+ if (!isChannelTargetKey(channelTarget)) {
91
+ throw new Error(
92
+ `Unknown channel target "${channelTarget}". Expected one of: ${CHANNEL_TARGET_KEYS.join(', ')}`
93
+ );
94
+ }
95
+ return CHANNEL_TARGETS[channelTarget];
96
+ }
97
+
98
+ /**
99
+ * Default customer-facing description when --description is omitted.
100
+ * @returns {string}
101
+ */
102
+ function getDefaultChannelDescription() {
103
+ return (
104
+ 'Enterprise AI assistant for employees. Provides access to approved assistants, ' +
105
+ 'enterprise knowledge, business workflows, and governed actions through AI Fabrix.'
106
+ );
107
+ }
108
+
109
+ module.exports = {
110
+ CHANNEL_TARGETS,
111
+ CHANNEL_TARGET_KEYS,
112
+ isChannelTargetKey,
113
+ getChannelTarget,
114
+ getDefaultChannelDescription
115
+ };
package/lib/cli/index.js CHANGED
@@ -22,6 +22,7 @@ const { setupExternalSystemCommands } = require('./setup-external-system');
22
22
  const { setupAppCommands: setupAppManagementCommands } = require('../commands/app');
23
23
  const { setupDatasourceCommands } = require('../commands/datasource');
24
24
  const { setupDimensionCommands } = require('../commands/dimension');
25
+ const { setupResourceTypeCommands } = require('../commands/resource-type');
25
26
  const { setupIdentityCommands } = require('../commands/identity');
26
27
  const { setupProtectionCommands } = require('../commands/protection');
27
28
  const { setupCredentialDeploymentCommands } = require('./setup-credential-deployment');
@@ -29,6 +30,20 @@ const { setupIntegrationClientCommands } = require('./setup-integration-client')
29
30
  const { setupPlatformCommands } = require('./setup-platform');
30
31
  const { setupCertificationCommands } = require('./setup-certification-commands');
31
32
  const { setupGovernanceCommands } = require('../commands/governance');
33
+ const { setupChannelCommands } = require('../commands/channel');
34
+ const { setupNpmCommands } = require('./setup-npm');
35
+
36
+ /**
37
+ * Dev, secrets, parameters, and platform commands (keeps setupCommands under statement limit).
38
+ * @param {import('commander').Command} program
39
+ */
40
+ function setupDevAndPlatformCommands(program) {
41
+ setupDevCommands(program);
42
+ setupSecretsCommands(program);
43
+ setupNpmCommands(program);
44
+ setupParametersCommands(program);
45
+ setupPlatformCommands(program);
46
+ }
32
47
 
33
48
  /**
34
49
  * Sets up all CLI commands on the Commander program instance
@@ -42,18 +57,17 @@ function setupCommands(program) {
42
57
  setupGovernanceCommands(program);
43
58
  setupEnvironmentCommands(program);
44
59
  setupAppManagementCommands(program);
60
+ setupChannelCommands(program);
45
61
  setupDatasourceCommands(program);
46
62
  setupDimensionCommands(program);
63
+ setupResourceTypeCommands(program);
47
64
  setupIdentityCommands(program);
48
65
  setupProtectionCommands(program);
49
66
  setupUtilityCommands(program);
50
67
  setupCredentialDeploymentCommands(program);
51
68
  setupIntegrationClientCommands(program);
52
69
  setupExternalSystemCommands(program);
53
- setupDevCommands(program);
54
- setupSecretsCommands(program);
55
- setupParametersCommands(program);
56
- setupPlatformCommands(program);
70
+ setupDevAndPlatformCommands(program);
57
71
  }
58
72
 
59
73
  module.exports = {
@@ -11,7 +11,7 @@
11
11
  const chalk = require('chalk');
12
12
  const logger = require('../utils/logger');
13
13
  const config = require('../core/config');
14
- const { resolvePlatformControllerUrl } = require('../utils/platform-controller-url');
14
+ const { resolvePlatformControllerUrl, resolveCliPlatformControllerUrl } = require('../utils/platform-controller-url');
15
15
  const { withMutedLogger } = require('../utils/with-muted-logger');
16
16
  const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
17
17
  const { isAuthenticationError } = require('../utils/cli-utils');
@@ -19,29 +19,45 @@ const { resolveControllerUrl, isPlatformAuthValidForController } = require('../u
19
19
 
20
20
  /**
21
21
  * @param {Object} authOpts
22
- * @returns {Promise<string>}
22
+ * @returns {Promise<{ apiUrl: string, tokenUrl: string }>}
23
23
  */
24
- async function resolveGuidedAuthControllerUrl(authOpts = {}) {
25
- return (
24
+ async function resolveGuidedAuthControllerUrls(authOpts = {}) {
25
+ const tokenUrl =
26
26
  authOpts.platformControllerUrl ||
27
27
  (await config.getPlatformControllerUrl()) ||
28
- (await resolvePlatformControllerUrl())
29
- );
28
+ (await resolvePlatformControllerUrl());
29
+ const remoteServer = await config.getRemoteServer();
30
+ let apiUrl = tokenUrl;
31
+ if (remoteServer && String(remoteServer).trim()) {
32
+ apiUrl = await resolveCliPlatformControllerUrl();
33
+ }
34
+ return { apiUrl, tokenUrl };
35
+ }
36
+
37
+ /**
38
+ * @param {Object} authOpts
39
+ * @returns {Promise<string>}
40
+ */
41
+ async function resolveGuidedAuthControllerUrl(authOpts = {}) {
42
+ const { apiUrl } = await resolveGuidedAuthControllerUrls(authOpts);
43
+ return apiUrl;
30
44
  }
31
45
 
32
46
  /**
33
47
  * @async
34
48
  * @param {Function} handleLoginFn
35
- * @param {string} controllerUrl
36
- * @param {{ skipDataplaneVersionRefresh?: boolean }} [opts]
49
+ * @param {string} controllerUrl - Controller API base URL (may be localhost on builder host)
50
+ * @param {{ skipDataplaneVersionRefresh?: boolean, tokenControllerUrl?: string }} [opts]
37
51
  * @returns {Promise<void>}
38
52
  */
39
53
  async function runGuidedDeviceLogin(handleLoginFn, controllerUrl, opts = {}) {
40
54
  await handleLoginFn({
41
55
  method: 'device',
42
56
  controller: controllerUrl,
57
+ tokenControllerUrl: opts.tokenControllerUrl,
43
58
  environment: 'dev',
44
59
  compact: true,
60
+ skipControllerUrlPersist: Boolean(opts.tokenControllerUrl),
45
61
  skipDataplaneVersionRefresh: opts.skipDataplaneVersionRefresh === true,
46
62
  exitOnFailure: false
47
63
  });
@@ -56,20 +72,27 @@ async function runGuidedDeviceLogin(handleLoginFn, controllerUrl, opts = {}) {
56
72
  */
57
73
  async function runGuidedAuthStep(handleLoginFn, authOpts, spinners) {
58
74
  const spin = spinners.startSpinner('Authenticating...');
59
- const controllerUrl = await resolveGuidedAuthControllerUrl(authOpts);
75
+ const { apiUrl, tokenUrl } = await resolveGuidedAuthControllerUrls(authOpts);
76
+ const loginOpts = { skipDataplaneVersionRefresh: true, tokenControllerUrl: tokenUrl };
60
77
  const skipLogin =
61
78
  authOpts.skipLoginIfAuthenticated === true ||
62
- (await isPlatformAuthValidForController(controllerUrl));
79
+ (await isPlatformAuthValidForController(tokenUrl));
63
80
  try {
64
81
  if (skipLogin) {
65
82
  spinners.stopSpinnerSuccess(spin, 'Already authenticated');
66
83
  return;
67
84
  }
68
- await runGuidedDeviceLogin(handleLoginFn, controllerUrl, { skipDataplaneVersionRefresh: true });
85
+ await runGuidedDeviceLogin(handleLoginFn, apiUrl, loginOpts);
69
86
  } catch (authErr) {
70
87
  if (spin) spin.fail('Authentication required');
88
+ const retriable =
89
+ isAuthenticationError(authErr) ||
90
+ /device code|authorization_pending|complete sign-in|browser/i.test(String(authErr.message || ''));
91
+ if (!retriable) {
92
+ throw authErr;
93
+ }
71
94
  logger.error(formatBlockingError('Authentication required. Complete sign-in in your browser.'));
72
- await runGuidedDeviceLogin(handleLoginFn, controllerUrl, { skipDataplaneVersionRefresh: true });
95
+ await runGuidedDeviceLogin(handleLoginFn, apiUrl, loginOpts);
73
96
  }
74
97
  spinners.stopSpinnerSuccess(spin, 'Authenticated');
75
98
  }
@@ -102,6 +125,7 @@ async function runGuidedDataplaneWithAuthRecovery(options, handleUpDataplane, ha
102
125
  }
103
126
 
104
127
  module.exports = {
128
+ resolveGuidedAuthControllerUrls,
105
129
  resolveGuidedAuthControllerUrl,
106
130
  runGuidedDeviceLogin,
107
131
  runGuidedAuthStep,