@aifabrix/builder 2.51.1 → 2.52.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 (187) hide show
  1. package/jest.isolated-projects.js +25 -2
  2. package/jest.projects.js +4 -1
  3. package/lib/api/datasources-extended.api.js +8 -8
  4. package/lib/api/keycloak.api.js +92 -0
  5. package/lib/api/token-providers.api.js +77 -0
  6. package/lib/api/types/keycloak.types.js +23 -0
  7. package/lib/api/types/token-providers.types.js +65 -0
  8. package/lib/app/down.js +16 -1
  9. package/lib/app/run-container-start.js +4 -0
  10. package/lib/app/run-deploy-job.js +135 -27
  11. package/lib/app/run-env-compose.js +32 -0
  12. package/lib/app/run-env-recovery.js +35 -0
  13. package/lib/app/run-helpers.js +5 -6
  14. package/lib/app/run.js +11 -3
  15. package/lib/channels/add-channel.js +126 -19
  16. package/lib/channels/approval-guides/chatgpt.js +12 -9
  17. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  18. package/lib/channels/channel-add-ensure.js +128 -0
  19. package/lib/channels/channel-artifact-generator.js +11 -1
  20. package/lib/channels/channel-branding.js +94 -0
  21. package/lib/channels/channel-credential.js +8 -0
  22. package/lib/channels/channel-discovery.js +330 -0
  23. package/lib/channels/channel-external-system.js +1 -1
  24. package/lib/channels/channel-runtime-resolver.js +103 -14
  25. package/lib/channels/channel-status-display.js +7 -1
  26. package/lib/channels/channel-targets.js +18 -1
  27. package/lib/channels/chatgpt-runtime-contract.js +1 -1
  28. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  29. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  30. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  31. package/lib/channels/microsoft-copilot-icons.js +74 -0
  32. package/lib/channels/microsoft-copilot-limits.js +84 -0
  33. package/lib/channels/microsoft-copilot-package.js +130 -49
  34. package/lib/channels/microsoft-copilot-validate.js +197 -0
  35. package/lib/cli/index.js +2 -0
  36. package/lib/cli/infra-guided-footers.js +1 -1
  37. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  38. package/lib/cli/infra-guided.js +78 -62
  39. package/lib/cli/setup-app.help.js +4 -0
  40. package/lib/cli/setup-app.test-commands.js +59 -28
  41. package/lib/cli/setup-external-system.js +50 -12
  42. package/lib/cli/setup-infra.js +31 -4
  43. package/lib/commands/channel-add-setup.js +35 -10
  44. package/lib/commands/channel.js +27 -12
  45. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  46. package/lib/commands/governance-verify-external.js +2 -5
  47. package/lib/commands/integration-definition.js +158 -0
  48. package/lib/commands/lifecycle-external.js +14 -7
  49. package/lib/commands/repair-auth-apply.js +19 -5
  50. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  51. package/lib/commands/role-assistant-test-output.js +200 -0
  52. package/lib/commands/role-assistant.js +131 -114
  53. package/lib/commands/test-governance-external.js +2 -5
  54. package/lib/commands/up-miso.js +61 -24
  55. package/lib/commands/upload-data-sync-finalize.js +20 -3
  56. package/lib/commands/verify-governance-external.js +2 -5
  57. package/lib/commands/verify-operations-external.js +2 -5
  58. package/lib/commands/verify-operations-step-plan.js +31 -0
  59. package/lib/commands/verify-trust-command-action.js +2 -5
  60. package/lib/datasource/capability/capability-resolve.js +15 -5
  61. package/lib/datasource/capability/copy-operations.js +14 -4
  62. package/lib/datasource/datasource-identifier.js +4 -3
  63. package/lib/datasource/integration-context.js +3 -3
  64. package/lib/datasource/resolve-app.js +90 -8
  65. package/lib/datasource/test-e2e.js +2 -2
  66. package/lib/datasource/unified-validation-run-body.js +15 -1
  67. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  68. package/lib/external-system/deploy-helpers.js +9 -5
  69. package/lib/external-system/deploy.js +2 -1
  70. package/lib/external-system/test-system-level.js +21 -3
  71. package/lib/external-system/test.js +4 -4
  72. package/lib/generator/builders.js +9 -15
  73. package/lib/infrastructure/index.js +19 -4
  74. package/lib/integration-definition/apply.js +478 -0
  75. package/lib/integration-definition/canonical-json.js +51 -0
  76. package/lib/integration-definition/emit-suite-bid.js +94 -0
  77. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  78. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  79. package/lib/integration-definition/from-discovery.js +339 -0
  80. package/lib/integration-definition/hash.js +37 -0
  81. package/lib/integration-definition/index.js +37 -0
  82. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  83. package/lib/integration-definition/resource-type-catalog.js +59 -0
  84. package/lib/integration-definition/system-kind-enum.js +40 -0
  85. package/lib/integration-definition/validate.js +148 -0
  86. package/lib/programmatic/builder-api-request.js +5 -1
  87. package/lib/programmatic/builder-help-governance.js +3 -2
  88. package/lib/programmatic/capability-registry.js +19 -0
  89. package/lib/programmatic/openapi-component-schemas.js +6 -1
  90. package/lib/programmatic/openapi-descriptions.js +40 -0
  91. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  92. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  93. package/lib/programmatic/openapi-request-schemas.js +14 -0
  94. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  95. package/lib/programmatic/openapi-result-schemas.js +46 -99
  96. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  97. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  98. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  99. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  100. package/lib/programmatic/openapi-spec.js +11 -1
  101. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  102. package/lib/programmatic/route-handler-map.js +29 -1
  103. package/lib/programmatic/run-channel-package.js +236 -0
  104. package/lib/programmatic/run-integration-definition.js +69 -0
  105. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  106. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  107. package/lib/role-assistant/knowledge-sync.js +104 -89
  108. package/lib/role-assistant/test-cases-bid.js +155 -0
  109. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  110. package/lib/role-assistant/test-cases-honesty.js +24 -10
  111. package/lib/role-assistant/test-cases.js +28 -0
  112. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  113. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  114. package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
  115. package/lib/role-assistant/test-runner-workhub.js +121 -25
  116. package/lib/role-assistant/test-suite.js +119 -0
  117. package/lib/schema/application-schema.json +64 -32
  118. package/lib/schema/external-datasource.schema.json +103 -17
  119. package/lib/schema/external-system.schema.json +54 -3
  120. package/lib/schema/infra.parameter.yaml +0 -20
  121. package/lib/schema/integration-definition.schema.json +195 -0
  122. package/lib/utils/authentication-shape.js +6 -0
  123. package/lib/utils/certify-external-workspace.js +40 -0
  124. package/lib/utils/docker-build.js +3 -3
  125. package/lib/utils/docker-run-ephemeral.js +77 -17
  126. package/lib/utils/health-check-public-warn.js +31 -13
  127. package/lib/utils/health-check.js +11 -1
  128. package/lib/utils/help-builder.js +1 -0
  129. package/lib/utils/paths.js +13 -15
  130. package/lib/utils/promote-dev-docker-image.js +97 -18
  131. package/lib/utils/role-assistant-paths.js +76 -14
  132. package/lib/utils/transient-developer-id.js +28 -0
  133. package/lib/utils/upload-sync-options.js +7 -4
  134. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  135. package/lib/validation/validate.js +54 -19
  136. package/package.json +1 -1
  137. package/scripts/pnpm/af.mjs +24 -9
  138. package/scripts/pnpm/help.mjs +1 -1
  139. package/templates/applications/builder-api/application.yaml +3 -3
  140. package/templates/applications/dataplane/application.yaml +5 -5
  141. package/templates/applications/dataplane/env.template +8 -7
  142. package/templates/applications/dataplane/rbac.yaml +10 -10
  143. package/templates/applications/keycloak/application.yaml +2 -2
  144. package/templates/applications/keycloak/env.template +5 -2
  145. package/templates/applications/miso-controller/application.yaml +25 -11
  146. package/templates/applications/miso-controller/env.template +24 -5
  147. package/templates/channels/README.md +11 -1
  148. package/templates/channels/brand/color-192.png +0 -0
  149. package/templates/channels/brand/dark/128.png +0 -0
  150. package/templates/channels/brand/dark/192.png +0 -0
  151. package/templates/channels/brand/dark/256.png +0 -0
  152. package/templates/channels/brand/dark/32.png +0 -0
  153. package/templates/channels/brand/dark/512.png +0 -0
  154. package/templates/channels/brand/dark/64.png +0 -0
  155. package/templates/channels/brand/light/128.png +0 -0
  156. package/templates/channels/brand/light/192.png +0 -0
  157. package/templates/channels/brand/light/256.png +0 -0
  158. package/templates/channels/brand/light/32.png +0 -0
  159. package/templates/channels/brand/light/512.png +0 -0
  160. package/templates/channels/brand/light/64.png +0 -0
  161. package/templates/channels/brand/mono-white/128.png +0 -0
  162. package/templates/channels/brand/mono-white/192.png +0 -0
  163. package/templates/channels/brand/mono-white/256.png +0 -0
  164. package/templates/channels/brand/mono-white/32.png +0 -0
  165. package/templates/channels/brand/mono-white/512.png +0 -0
  166. package/templates/channels/brand/mono-white/64.png +0 -0
  167. package/templates/channels/brand/outline-32.png +0 -0
  168. package/templates/channels/chatgpt/install.template.md.hbs +76 -36
  169. package/templates/channels/chatgpt/integration/README.md +4 -4
  170. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  171. package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
  172. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  173. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  174. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  175. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  176. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  177. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  178. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  179. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  180. package/templates/channels/slack/install.template.md.hbs +6 -1
  181. package/templates/channels/slack/integration/README.md +2 -4
  182. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  183. package/templates/channels/teams-bot/integration/README.md +3 -5
  184. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  185. package/templates/python/docker-compose.hbs +10 -0
  186. package/templates/typescript/docker-compose.hbs +10 -0
  187. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Last-moment run env recovery for the shared applications workspace.
3
+ *
4
+ * @fileoverview Builds the callback used immediately before Docker Compose starts
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const runEnvCompose = require('./run-env-compose');
12
+
13
+ /**
14
+ * Build a recovery hook for run env files in the shared workspace.
15
+ * @param {string} appName - Application name
16
+ * @param {Object} prepared - Prepared run context
17
+ * @returns {Function} Async recovery hook
18
+ */
19
+ function buildRunEnvRecovery(appName, prepared) {
20
+ return () =>
21
+ runEnvCompose.ensureRunEnvFilesPresent(
22
+ appName,
23
+ prepared.appConfig,
24
+ prepared.runEnvPath,
25
+ prepared.runEnvAdminPath,
26
+ prepared.appConfig.developerId,
27
+ {
28
+ runEnvKey: prepared.mergedRunOptions.env,
29
+ effectiveEnvironmentScopedResources:
30
+ prepared.mergedRunOptions.effectiveEnvironmentScopedResources
31
+ }
32
+ );
33
+ }
34
+
35
+ module.exports = { buildRunEnvRecovery };
@@ -253,18 +253,17 @@ async function checkInfraHealthOrThrow(debug, appConfig) {
253
253
  }
254
254
 
255
255
  /**
256
- * Ensure dev directory exists
256
+ * Refresh the shared developer run directory for the current application.
257
+ *
258
+ * The applications-dev-* directory is shared by all applications. Always copy
259
+ * the selected app so files from a previous run cannot leak into this run.
257
260
  * @async
258
261
  * @param {string} appName - Application name
259
262
  * @param {string} developerId - Developer ID
260
263
  * @returns {Promise<string>} Dev directory path
261
264
  */
262
265
  async function ensureDevDirectory(appName, developerId) {
263
- const devDir = buildCopy.getDevDirectory(appName, developerId);
264
- if (!fsSync.existsSync(devDir)) {
265
- await buildCopy.copyBuilderToDevDirectory(appName, developerId);
266
- }
267
- return devDir;
266
+ return buildCopy.copyBuilderToDevDirectory(appName, developerId);
268
267
  }
269
268
 
270
269
  /**
package/lib/app/run.js CHANGED
@@ -28,6 +28,7 @@ const helpers = require('./run-helpers');
28
28
  const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
29
29
  const { logRestartDevMountSummary } = require('./restart-display');
30
30
  const { checkAndStopContainer } = require('./run-stop-containers');
31
+ const { buildRunEnvRecovery } = require('./run-env-recovery');
31
32
  const { execWithDockerEnv } = require('../utils/docker-exec');
32
33
  const { resolveRunProxyPersistEnabled, resolveRunEffectiveNoProxy } = require('../utils/run-cli-flags');
33
34
 
@@ -178,7 +179,8 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
178
179
  runEnvPath = null,
179
180
  runEnvAdminPath = null,
180
181
  runOptions = {},
181
- devMountPath = null
182
+ devMountPath = null,
183
+ ensureRunEnvFiles = null
182
184
  } = opts;
183
185
  const misoEnvironment = opts.misoEnvironment ?? composeGenerator.resolveMisoEnvironment(runOptions);
184
186
  try {
@@ -188,6 +190,7 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
188
190
  runEnvAdminPath,
189
191
  runOptions,
190
192
  devMountPath,
193
+ ensureRunEnvFiles,
191
194
  misoEnvironment
192
195
  });
193
196
  await helpers.displayRunStatus(
@@ -410,15 +413,20 @@ async function runApp(appName, options = {}) {
410
413
  fullImage: prepared.mergedRunOptions.image,
411
414
  runEnvPath: prepared.runEnvPath,
412
415
  devMountPath: prepared.devMountPath || null,
413
- debug
416
+ debug,
417
+ // Guided installers already show a top-level spinner; keep migrate/deployJob logs out of TTY.
418
+ quiet:
419
+ options.silentPoll === true || typeof options.onPollText === 'function'
414
420
  });
421
+ const ensureRunEnvFiles = buildRunEnvRecovery(appName, prepared);
415
422
  await startAppContainer(appName, prepared.tempComposePath, prepared.hostPort, prepared.appConfig, {
416
423
  debug,
417
424
  runEnvPath: prepared.runEnvPath,
418
425
  runEnvAdminPath: prepared.runEnvAdminPath,
419
426
  runOptions: prepared.mergedRunOptions,
420
427
  devMountPath: prepared.devMountPath,
421
- runScopeOpts: prepared.runScopeOpts
428
+ runScopeOpts: prepared.runScopeOpts,
429
+ ensureRunEnvFiles
422
430
  });
423
431
  } catch (error) {
424
432
  if (debug) {
@@ -10,9 +10,15 @@
10
10
 
11
11
  const { getChannelTarget, getDefaultChannelDescription } = require('./channel-targets');
12
12
  const { resolveAppDescription } = require('./channel-description');
13
+ const path = require('path');
13
14
  const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
14
15
  const { generateChannelArtifact } = require('./channel-artifact-generator');
15
16
  const { fetchRuntimeLayerBToolNames } = require('./runtime-tool-catalog');
17
+ const { loadChannelPackageContext } = require('./channel-discovery');
18
+ const {
19
+ readChannelBranding,
20
+ mergeChannelBranding
21
+ } = require('./channel-branding');
16
22
  const {
17
23
  getExistingChannelExtension,
18
24
  buildChannelExtensionBlock,
@@ -39,9 +45,14 @@ const {
39
45
  } = require('./channel-display-name');
40
46
  const {
41
47
  displayExistingChannelStatus,
42
- displayChannelInstallResult,
43
- displayChannelError
48
+ displayChannelInstallResult
44
49
  } = require('./channel-status-display');
50
+ const { ensureChannelReadyForAdd } = require('./channel-add-ensure');
51
+ const {
52
+ assertMicrosoftCopilotOauthReference,
53
+ assertMicrosoftCopilotOauthReferenceBeforeForce,
54
+ loadMicrosoftCopilotEntraContext
55
+ } = require('./microsoft-copilot-channel-add');
45
56
 
46
57
  /**
47
58
  * @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
@@ -252,6 +263,9 @@ async function completeLiveInstall(params) {
252
263
  capabilities: target.capabilities,
253
264
  artifactPath: artifact.outputPath,
254
265
  installGuidePath: artifact.installPath,
266
+ zipPath: artifact.zipPath,
267
+ installReady: artifact.installReady,
268
+ blocker: artifact.blocker,
255
269
  validation: toValidationDisplay(validation),
256
270
  approvalGuide
257
271
  });
@@ -272,7 +286,10 @@ function finishDryRunInstall(params) {
272
286
  credentialKey: params.target.credentialKey,
273
287
  capabilities: params.target.capabilities,
274
288
  artifactPath: params.artifact.outputPath,
275
- installGuidePath: params.artifact.installPath
289
+ installGuidePath: params.artifact.installPath,
290
+ zipPath: params.artifact.zipPath,
291
+ installReady: params.artifact.installReady,
292
+ blocker: params.artifact.blocker
276
293
  });
277
294
  return { dryRun: true, channelBlock: params.channelBlock, artifact: params.artifact };
278
295
  }
@@ -281,12 +298,78 @@ function finishDryRunInstall(params) {
281
298
  * @param {Object} params
282
299
  * @returns {Promise<{ artifact: Object, channelBlock: Object, status: string }>}
283
300
  */
301
+ /**
302
+ * @param {Object|null} packageContext
303
+ * @param {Object} options
304
+ * @param {'iconColorPath'|'iconOutlinePath'} key
305
+ * @returns {string|undefined}
306
+ */
307
+ function brandingIconOrOption(packageContext, options, key) {
308
+ if (typeof options[key] === 'string' && options[key].trim()) {
309
+ return options[key].trim();
310
+ }
311
+ if (packageContext && typeof packageContext[key] === 'string' && packageContext[key].trim()) {
312
+ return packageContext[key].trim();
313
+ }
314
+ return undefined;
315
+ }
316
+
317
+ async function resolvePackageContextForInstall(params) {
318
+ const { channelTarget, options, name, description, ctx } = params;
319
+ const packagingTargets = new Set(['chatgpt', 'microsoft-copilot', 'slack', 'teams-bot']);
320
+ if (!packagingTargets.has(channelTarget)) {
321
+ return null;
322
+ }
323
+ const projectRoot = options.projectRoot || process.cwd();
324
+ const workspaceDir = path.join(projectRoot, 'integration', channelTarget);
325
+ const branding = mergeChannelBranding(readChannelBranding(workspaceDir), {
326
+ displayName: options.overrideDisplayName || undefined,
327
+ description: options.overrideDescription || undefined,
328
+ iconColorPath: options.iconColorPath || undefined,
329
+ iconOutlinePath: options.iconOutlinePath || undefined
330
+ });
331
+ let packageContext;
332
+ try {
333
+ packageContext = await loadChannelPackageContext({
334
+ dataplaneBaseUrl: ctx.dataplaneUrl,
335
+ authConfig: ctx.authConfig,
336
+ channelTarget,
337
+ branding
338
+ });
339
+ } catch {
340
+ packageContext = {
341
+ displayName: name,
342
+ description,
343
+ governedToolNames: [],
344
+ mcpServerUrl: null,
345
+ installReady: false,
346
+ blocker: 'discoveryUnavailable',
347
+ authentication: null,
348
+ authenticationCapabilities: []
349
+ };
350
+ }
351
+ return {
352
+ ...packageContext,
353
+ iconColorPath: branding.iconColorPath,
354
+ iconOutlinePath: branding.iconOutlinePath
355
+ };
356
+ }
357
+
284
358
  async function buildInstallArtifacts(params) {
285
359
  const { channelTarget, options, target, existing, name, baseName, description, ctx } = params;
286
- const runtimeToolNames =
287
- channelTarget === 'chatgpt' || channelTarget === 'microsoft-copilot'
288
- ? await fetchRuntimeLayerBToolNames(ctx.dataplaneUrl, ctx.authConfig, channelTarget)
289
- : [];
360
+ const packageContext = await resolvePackageContextForInstall(params);
361
+ let runtimeToolNames = [];
362
+ if (channelTarget === 'chatgpt' || channelTarget === 'microsoft-copilot') {
363
+ runtimeToolNames =
364
+ packageContext?.governedToolNames?.length > 0
365
+ ? packageContext.governedToolNames
366
+ : await fetchRuntimeLayerBToolNames(ctx.dataplaneUrl, ctx.authConfig, channelTarget);
367
+ }
368
+ let entraProvider = null;
369
+ let entraIdentityProvider = null;
370
+ if (channelTarget === 'microsoft-copilot') {
371
+ ({ entraProvider, entraIdentityProvider } = await loadMicrosoftCopilotEntraContext(ctx));
372
+ }
290
373
  const runtimeValues = resolveChannelRuntimeValues({
291
374
  dataplaneBaseUrl: ctx.dataplaneUrl,
292
375
  channelTarget,
@@ -296,7 +379,14 @@ async function buildInstallArtifacts(params) {
296
379
  appDescription: description,
297
380
  icon: options.icon,
298
381
  commandPrefix: options.commandPrefix,
299
- layerBToolNames: runtimeToolNames
382
+ layerBToolNames: runtimeToolNames,
383
+ packageContext,
384
+ entraProvider,
385
+ entraIdentityProvider,
386
+ oauthPluginVaultReferenceId: options.oauthPluginVaultReferenceId,
387
+ scopes: options.scopes,
388
+ iconColorPath: brandingIconOrOption(packageContext, options, 'iconColorPath'),
389
+ iconOutlinePath: brandingIconOrOption(packageContext, options, 'iconOutlinePath')
300
390
  });
301
391
  const projectRoot = options.projectRoot || process.cwd();
302
392
  const artifact = generateArtifactWithAudit(channelTarget, {
@@ -313,17 +403,39 @@ async function buildInstallArtifacts(params) {
313
403
  return { artifact, channelBlock, status };
314
404
  }
315
405
 
406
+ /**
407
+ * @param {string} channelTarget
408
+ * @param {Object} options
409
+ * @returns {Object}
410
+ */
411
+ function assertChannelAddOptions(channelTarget, options) {
412
+ const target = getChannelTarget(channelTarget);
413
+ if (target.experimental) {
414
+ throw new Error(
415
+ `Channel target "${channelTarget}" is experimental. Confirm product requirement before use.`
416
+ );
417
+ }
418
+ if (!options.name || typeof options.name !== 'string' || !options.name.trim()) {
419
+ throw new Error('--name is required for channel add');
420
+ }
421
+ return target;
422
+ }
423
+
316
424
  /**
317
425
  * @param {string} channelTarget
318
426
  * @param {Object} options
319
427
  * @returns {Promise<Object>}
320
428
  */
321
429
  async function runAddChannel(channelTarget, options = {}) {
322
- const { target, name, baseName, description, ctx, existing } = await prepareChannelContext(
430
+ const target = assertChannelAddOptions(channelTarget, options);
431
+ if (channelTarget === 'microsoft-copilot' && options.force === true) {
432
+ await assertMicrosoftCopilotOauthReferenceBeforeForce(options);
433
+ }
434
+ await ensureChannelReadyForAdd(channelTarget, options);
435
+ const { name, baseName, description, ctx, existing } = await prepareChannelContext(
323
436
  channelTarget,
324
437
  options
325
438
  );
326
-
327
439
  const skipped = showExistingChannelStatus({
328
440
  existing,
329
441
  options,
@@ -333,7 +445,9 @@ async function runAddChannel(channelTarget, options = {}) {
333
445
  ctx
334
446
  });
335
447
  if (skipped) return skipped;
336
-
448
+ if (channelTarget === 'microsoft-copilot') {
449
+ await assertMicrosoftCopilotOauthReference({ options, ctx, name, baseName, description });
450
+ }
337
451
  const { artifact, channelBlock, status } = await buildInstallArtifacts({
338
452
  channelTarget,
339
453
  options,
@@ -344,11 +458,9 @@ async function runAddChannel(channelTarget, options = {}) {
344
458
  description,
345
459
  ctx
346
460
  });
347
-
348
461
  if (options.dryRun) {
349
462
  return finishDryRunInstall({ channelTarget, name, baseName, target, artifact, channelBlock });
350
463
  }
351
-
352
464
  return completeLiveInstall({
353
465
  ctx,
354
466
  channelTarget,
@@ -367,12 +479,7 @@ async function runAddChannel(channelTarget, options = {}) {
367
479
  * @param {Object} options
368
480
  */
369
481
  async function addChannelCommand(channelTarget, options = {}) {
370
- try {
371
- await runAddChannel(channelTarget, options);
372
- } catch (err) {
373
- displayChannelError(err.message || String(err));
374
- throw err;
375
- }
482
+ await runAddChannel(channelTarget, options);
376
483
  }
377
484
 
378
485
  module.exports = {
@@ -16,17 +16,20 @@ const { formatCredentialPortalStep } = require('./credential-portal');
16
16
  */
17
17
  function buildChatGptApprovalGuide(ctx) {
18
18
  return {
19
- portalUrl: 'https://platform.openai.com/',
19
+ portalUrl: 'https://chatgpt.com/',
20
20
  steps: [
21
- 'Sign in to OpenAI Platform (https://platform.openai.com/) or your ChatGPT Enterprise workspace admin console.',
22
- 'Enable Developer mode / custom MCP app creation for the workspace (if policy requires admin allow-listing).',
23
- 'Create a custom MCP app using the generated channel-chatgpt.json reference artifact (path shown below).',
24
- 'Configure the Runtime MCP endpoint from INSTALL.md and verify Runtime Layer B tool discovery.',
25
- 'Complete workspace authentication setup for the custom MCP app.',
26
- formatCredentialPortalStep(ctx, 'workspace credential references used by the ChatGPT channel'),
27
- 'Publish the app to your workspace and configure end-user access controls.',
21
+ 'Sign in to the ChatGPT web app as a workspace admin or owner.',
22
+ 'Enable Developer mode for custom MCP apps (Business: Workspace settings → Apps, or Settings → Apps → Advanced settings; Enterprise/Edu: grant Developer mode under Permissions & roles → Connected data first).',
23
+ 'Create a custom MCP app with the Remote MCP URL from INSTALL.md (channel-chatgpt.json is a deployment reference, not a plugin archive).',
24
+ 'Select OAuth (authorization-code + PKCE S256). Do not configure a custom API key for a remote custom MCP app.',
25
+ 'Scan tools, complete OAuth if prompted, review discovered governed tools, then save the app as a draft.',
26
+ formatCredentialPortalStep(
27
+ ctx,
28
+ `${ctx.credentialKey || 'channel'} credential references (not secrets to paste into ChatGPT)`
29
+ ),
30
+ 'Test representative workflows, then publish the app and configure workspace access and action controls.',
28
31
  `After ChatGPT lists the app as active, re-run: ${ctx.resumeCommand}`,
29
- 'Optional: use --probe for AI Fabrix-side runtime checks; OpenAI-side publication remains a manual admin responsibility.'
32
+ 'Optional: use --probe for AI Fabrix-side Runtime checks; ChatGPT Scan tools / OAuth / publication remain workspace-admin responsibilities.'
30
33
  ]
31
34
  };
32
35
  }
@@ -1,14 +1,16 @@
1
1
  /**
2
- * Microsoft Copilot channel admin approval steps (plan 152).
2
+ * Microsoft Copilot channel install / approval steps.
3
3
  *
4
- * @fileoverview Copilot Studio / M365 admin connector guide
4
+ * @fileoverview Teams custom-app upload guide aligned with install templates
5
5
  * @author AI Fabrix Team
6
6
  * @version 1.0.0
7
7
  */
8
8
 
9
9
  'use strict';
10
10
 
11
- const { formatCredentialPortalStep } = require('./credential-portal');
11
+ const {
12
+ ENTRA_SSO_REGISTRATION_URL
13
+ } = require('../microsoft-copilot-entra-sso-form');
12
14
 
13
15
  /**
14
16
  * @param {Object} ctx
@@ -16,16 +18,18 @@ const { formatCredentialPortalStep } = require('./credential-portal');
16
18
  */
17
19
  function buildMicrosoftCopilotApprovalGuide(ctx) {
18
20
  return {
19
- portalUrl: 'https://admin.cloud.microsoft.com/',
21
+ portalUrl: ENTRA_SSO_REGISTRATION_URL,
20
22
  steps: [
21
- 'Sign in to Microsoft 365 admin (https://admin.cloud.microsoft.com/) or Copilot Studio as a tenant admin.',
22
- 'Upload or sideload the generated appPackage.<env>.zip package artifact.',
23
- 'Review declarative agent + Remote MCP plugin details and approve tenant distribution.',
24
- 'Configure Entra/OAuth consent and user assignment for target groups.',
25
- formatCredentialPortalStep(ctx, 'Copilot channel credential references (no secrets in package files)'),
26
- 'Verify Runtime MCP endpoint and Layer B tool visibility using INSTALL.md guidance.',
27
- `After Copilot shows the package as available, re-run: ${ctx.resumeCommand}`,
28
- 'Optional: use --probe for AI Fabrix-side checks; tenant upload/approval remains Microsoft-admin responsibility.'
23
+ 'Open Microsoft Teams with your target Microsoft 365 account.',
24
+ 'Go to Apps → Manage your apps → Upload an app → Upload a custom app.',
25
+ 'Upload only appPackage.<env>.zip (do not upload individual manifest files or channel-microsoft-copilot.json).',
26
+ 'Review permissions, select Add, and open the installed app. Users stay on the Microsoft 365 Entra session — no separate AI Fabrix sign-in.',
27
+ 'If prompted, complete Entra admin consent for the Runtime MCP delegated scope.',
28
+ 'If Upload a custom app is unavailable, ask a Teams admin to enable custom app uploads or deploy the package centrally.',
29
+ `Register Entra SSO first at ${ENTRA_SSO_REGISTRATION_URL} using the Base URL / Client ID / Scope values printed by the CLI (or INSTALL.md). Pass the registration id as --oauth-plugin-vault-reference-id.`,
30
+ 'Do not configure AI Fabrix OAuth client secrets under Connected Systems → Authentication for this Entra SSO flow.',
31
+ 'Confirm Runtime MCP and governed tools using INSTALL.md, then re-run with --probe after Teams install.',
32
+ `After the app is available in Teams, re-run: ${ctx.resumeCommand}`
29
33
  ]
30
34
  };
31
35
  }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Ensure local scaffold + dataplane upload before channel add install.
3
+ *
4
+ * @fileoverview One-command channel add prelude (scaffold → upload if needed)
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const chalk = require('chalk');
14
+ const logger = require('../utils/logger');
15
+ const { formatSuccessLine, metadata } = require('../utils/cli-test-layout-chalk');
16
+ const { getIntegrationPath } = require('../utils/paths');
17
+ const { getExternalSystem } = require('../api/external-systems.api');
18
+ const { unwrapApiData } = require('../utils/external-system-readiness-core');
19
+ const { scaffoldChannelIntegration } = require('./channel-integration-generator');
20
+ const { resolveChannelAuthContext } = require('./channel-auth-context');
21
+ const { uploadExternalSystem } = require('../commands/upload');
22
+
23
+ /**
24
+ * @param {string} channelTarget
25
+ * @param {string} [projectRoot]
26
+ * @returns {string}
27
+ */
28
+ function resolveIntegrationDir(channelTarget, projectRoot) {
29
+ if (projectRoot) {
30
+ return path.join(path.resolve(projectRoot), 'integration', channelTarget);
31
+ }
32
+ return getIntegrationPath(channelTarget);
33
+ }
34
+
35
+ /**
36
+ * @param {Object} ctx
37
+ * @param {string} channelTarget
38
+ * @returns {Promise<boolean>}
39
+ */
40
+ async function isCommunicationSystemUploaded(ctx, channelTarget) {
41
+ const res = await getExternalSystem(ctx.dataplaneUrl, channelTarget, ctx.authConfig);
42
+ const system = unwrapApiData(res);
43
+ return !!(system && typeof system === 'object');
44
+ }
45
+
46
+ /**
47
+ * Scaffold integration/<target>/ when missing (does not overwrite).
48
+ *
49
+ * @param {string} channelTarget
50
+ * @param {Object} options
51
+ * @returns {Promise<{ scaffolded: boolean, integrationDir: string }>}
52
+ */
53
+ async function ensureChannelScaffold(channelTarget, options = {}) {
54
+ const integrationDir = resolveIntegrationDir(channelTarget, options.projectRoot);
55
+ if (fs.existsSync(integrationDir)) {
56
+ return { scaffolded: false, integrationDir };
57
+ }
58
+ const result = await scaffoldChannelIntegration(channelTarget, {
59
+ projectRoot: options.projectRoot,
60
+ allowExperimental: options.allowExperimental === true
61
+ });
62
+ if (options.quiet !== true) {
63
+ logger.log(formatSuccessLine(`Scaffolded integration at ${result.integrationDir}`));
64
+ for (const file of result.files) {
65
+ logger.log(metadata(file));
66
+ }
67
+ }
68
+ return { scaffolded: true, integrationDir: result.integrationDir };
69
+ }
70
+
71
+ /**
72
+ * Upload when missing on dataplane; with --force upload every time.
73
+ *
74
+ * @param {string} channelTarget
75
+ * @param {Object} options
76
+ * @returns {Promise<{ uploaded: boolean, skipped: boolean, ctx: Object|null }>}
77
+ */
78
+ async function ensureChannelUploaded(channelTarget, options = {}) {
79
+ if (options.dryRun === true) {
80
+ return { uploaded: false, skipped: true, ctx: null };
81
+ }
82
+ const ctx = await resolveChannelAuthContext(channelTarget, { silent: options.quiet === true });
83
+ const force = options.force === true;
84
+ const alreadyUploaded = force ? false : await isCommunicationSystemUploaded(ctx, channelTarget);
85
+ if (alreadyUploaded) {
86
+ if (options.quiet !== true) {
87
+ logger.log(chalk.gray(` Communication system "${channelTarget}" already on dataplane`));
88
+ }
89
+ return { uploaded: false, skipped: true, ctx };
90
+ }
91
+ if (options.quiet !== true) {
92
+ const reason = force ? 'force upload' : 'not found on dataplane';
93
+ logger.log(chalk.white(`Uploading ${channelTarget} (${reason})…`));
94
+ }
95
+ await uploadExternalSystem(channelTarget, {
96
+ force: true,
97
+ minimal: options.quiet === true || options.minimal !== false
98
+ });
99
+ if (options.quiet !== true) {
100
+ logger.log(formatSuccessLine(`Uploaded ${channelTarget}`));
101
+ }
102
+ return { uploaded: true, skipped: false, ctx };
103
+ }
104
+
105
+ /**
106
+ * Scaffold if needed, then upload if missing (or always when --force).
107
+ *
108
+ * @param {string} channelTarget
109
+ * @param {Object} options
110
+ * @returns {Promise<{ scaffolded: boolean, uploaded: boolean, integrationDir: string }>}
111
+ */
112
+ async function ensureChannelReadyForAdd(channelTarget, options = {}) {
113
+ const scaffold = await ensureChannelScaffold(channelTarget, options);
114
+ const upload = await ensureChannelUploaded(channelTarget, options);
115
+ return {
116
+ scaffolded: scaffold.scaffolded,
117
+ uploaded: upload.uploaded,
118
+ integrationDir: scaffold.integrationDir
119
+ };
120
+ }
121
+
122
+ module.exports = {
123
+ resolveIntegrationDir,
124
+ isCommunicationSystemUploaded,
125
+ ensureChannelScaffold,
126
+ ensureChannelUploaded,
127
+ ensureChannelReadyForAdd
128
+ };
@@ -147,11 +147,21 @@ function generateChannelArtifact(params) {
147
147
  params.dryRun === true
148
148
  );
149
149
 
150
+ const installReady = params.runtimeValues?.installReady === true;
151
+ const blocker = params.runtimeValues?.discoveryBlocker || null;
150
152
  return {
151
153
  outputPath,
152
154
  artifactHash,
153
155
  content,
154
- installPath: install?.installPath
156
+ installPath: install?.installPath,
157
+ zipPath: null,
158
+ links: {
159
+ readme: install?.installPath || null,
160
+ zip: null,
161
+ filesRoot: outputDir
162
+ },
163
+ installReady,
164
+ blocker
155
165
  };
156
166
  }
157
167
 
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Optional channel package branding in Builder workspace (plan 167.0).
3
+ *
4
+ * @fileoverview Read/write channel-branding.json under integration/<systemKey>/
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('../internal/fs-real-sync');
12
+ const path = require('path');
13
+
14
+ const BRANDING_FILENAME = 'channel-branding.json';
15
+
16
+ /**
17
+ * @param {string} workspaceSystemDir
18
+ * @returns {string}
19
+ */
20
+ function brandingFilePath(workspaceSystemDir) {
21
+ return path.join(workspaceSystemDir, BRANDING_FILENAME);
22
+ }
23
+
24
+ /**
25
+ * @param {string} workspaceSystemDir
26
+ * @returns {{ displayName?: string, description?: string, iconColorPath?: string, iconOutlinePath?: string }}
27
+ */
28
+ function readChannelBranding(workspaceSystemDir) {
29
+ const filePath = brandingFilePath(workspaceSystemDir);
30
+ if (!fs.existsSync(filePath)) {
31
+ return {};
32
+ }
33
+ const raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
34
+ if (!raw || typeof raw !== 'object') {
35
+ return {};
36
+ }
37
+ /** @type {{ displayName?: string, description?: string, iconColorPath?: string, iconOutlinePath?: string }} */
38
+ const out = {};
39
+ if (typeof raw.displayName === 'string' && raw.displayName.trim()) {
40
+ out.displayName = raw.displayName.trim();
41
+ }
42
+ if (typeof raw.description === 'string' && raw.description.trim()) {
43
+ out.description = raw.description.trim();
44
+ }
45
+ if (typeof raw.iconColorPath === 'string' && raw.iconColorPath.trim()) {
46
+ out.iconColorPath = raw.iconColorPath.trim();
47
+ }
48
+ if (typeof raw.iconOutlinePath === 'string' && raw.iconOutlinePath.trim()) {
49
+ out.iconOutlinePath = raw.iconOutlinePath.trim();
50
+ }
51
+ return out;
52
+ }
53
+
54
+ /**
55
+ * @param {string} workspaceSystemDir
56
+ * @param {Object} branding
57
+ * @returns {Object}
58
+ */
59
+ function writeChannelBranding(workspaceSystemDir, branding) {
60
+ fs.mkdirSync(workspaceSystemDir, { recursive: true });
61
+ const next = {
62
+ ...readChannelBranding(workspaceSystemDir),
63
+ ...(branding && typeof branding === 'object' ? branding : {})
64
+ };
65
+ const cleaned = {};
66
+ for (const key of ['displayName', 'description', 'iconColorPath', 'iconOutlinePath']) {
67
+ if (typeof next[key] === 'string' && next[key].trim()) {
68
+ cleaned[key] = next[key].trim();
69
+ }
70
+ }
71
+ fs.writeFileSync(brandingFilePath(workspaceSystemDir), `${JSON.stringify(cleaned, null, 2)}\n`, 'utf8');
72
+ return cleaned;
73
+ }
74
+
75
+ /**
76
+ * Merge CLI/API overrides over workspace branding.
77
+ * @param {Object} [workspaceBranding]
78
+ * @param {Object} [overrides]
79
+ * @returns {Object}
80
+ */
81
+ function mergeChannelBranding(workspaceBranding, overrides) {
82
+ return {
83
+ ...(workspaceBranding && typeof workspaceBranding === 'object' ? workspaceBranding : {}),
84
+ ...(overrides && typeof overrides === 'object' ? overrides : {})
85
+ };
86
+ }
87
+
88
+ module.exports = {
89
+ BRANDING_FILENAME,
90
+ brandingFilePath,
91
+ readChannelBranding,
92
+ writeChannelBranding,
93
+ mergeChannelBranding
94
+ };