@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
@@ -112,6 +112,49 @@ function normalizeExternalOptions(options) {
112
112
  return normalized;
113
113
  }
114
114
 
115
+ /**
116
+ * Whether normalized options include all external CLI fields for headless create.
117
+ * @param {Object} normalized - Normalized external create options
118
+ * @returns {boolean}
119
+ */
120
+ function hasFullExternalCliOptions(normalized) {
121
+ return Boolean(
122
+ normalized.systemKey &&
123
+ normalized.systemDisplayName &&
124
+ normalized.systemDescription &&
125
+ normalized.systemType &&
126
+ normalized.authType &&
127
+ normalized.entityType &&
128
+ normalized.datasourceCount
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Map positional systemKey and apply headless defaults for external create.
134
+ * @param {string} appName - Positional appKey or systemKey from CLI
135
+ * @param {Object} options - Raw CLI options
136
+ * @returns {Object} Normalized options ready for createApp
137
+ */
138
+ function prepareExternalCreateOptions(appName, options) {
139
+ const normalized = normalizeExternalOptions(options);
140
+ const isExternalType = options.type === 'external' || !options.type;
141
+
142
+ if (isExternalType && appName) {
143
+ normalized.systemKey = appName;
144
+ }
145
+
146
+ if (isExternalType && hasFullExternalCliOptions(normalized)) {
147
+ if (!Object.prototype.hasOwnProperty.call(normalized, 'github')) {
148
+ normalized.github = false;
149
+ }
150
+ if (!Object.prototype.hasOwnProperty.call(normalized, 'controller')) {
151
+ normalized.controller = false;
152
+ }
153
+ }
154
+
155
+ return normalized;
156
+ }
157
+
115
158
  /**
116
159
  * Validate required options for non-interactive external creation
117
160
  * @param {Object} normalizedOptions - Normalized options
@@ -149,7 +192,7 @@ async function handleCreateCommand(appName, options) {
149
192
  }
150
193
 
151
194
  const wizardOptions = { app: appName, ...options };
152
- const normalizedOptions = normalizeExternalOptions(options);
195
+ const normalizedOptions = prepareExternalCreateOptions(appName, options);
153
196
 
154
197
  const isExternalType = options.type === 'external' || !options.type;
155
198
  const isNonInteractive = process.stdin && process.stdin.isTTY === false;
@@ -279,7 +322,10 @@ function setupBuildRunLogsDownCommands(program) {
279
322
  .option('-f, --force-template', 'Force rebuild from template')
280
323
  .option('--no-cache', 'Full Docker rebuild (disable layer cache); use after Dockerfile or context fixes')
281
324
  .option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in application.yaml to match for deploy.')
282
- .option('--base', 'Also tag the manifest base image name (after developer-scoped build when developer id > 0)')
325
+ .option(
326
+ '--base',
327
+ 'When manifest omits image: also tag the base repository after a developer-scoped build (ignored when application.yaml defines image)'
328
+ )
283
329
  .action(async(appName, options) => {
284
330
  try {
285
331
  const imageTag = await app.buildApp(appName, options);
@@ -437,4 +483,9 @@ function setupAppCommands(program) {
437
483
  setupPushDeployDockerfileCommands(program);
438
484
  }
439
485
 
440
- module.exports = { setupAppCommands };
486
+ module.exports = {
487
+ setupAppCommands,
488
+ normalizeExternalOptions,
489
+ prepareExternalCreateOptions,
490
+ validateNonInteractiveExternalOptions
491
+ };
@@ -15,7 +15,7 @@ const { handleCommandError, isAuthenticationError } = require('../utils/cli-util
15
15
  const { resolveControllerUrl } = require('../utils/controller-url');
16
16
  const { handleLogin } = require('../commands/login');
17
17
  const { handleUpDataplane } = require('../commands/up-dataplane');
18
- const { cleanBuilderAppDirs } = require('../commands/up-common');
18
+ const { cleanBuilderAppDirs, DATAPLANE_BUILDER_APP_KEYS } = require('../commands/up-common');
19
19
  const { runGuidedUpDataplane } = require('./infra-guided');
20
20
  const { recordInfraInstallationCommand } = require('./installation-log-command');
21
21
 
@@ -26,7 +26,7 @@ const { recordInfraInstallationCommand } = require('./installation-log-command')
26
26
  async function runUpDataplaneWork(options) {
27
27
  let cleanedAppKeys = [];
28
28
  if (options.force) {
29
- const c = await cleanBuilderAppDirs(['dataplane']);
29
+ const c = await cleanBuilderAppDirs([...DATAPLANE_BUILDER_APP_KEYS]);
30
30
  cleanedAppKeys = Array.isArray(c) ? c : [];
31
31
  }
32
32
  if (!options.verbose) {
@@ -19,6 +19,7 @@ const { handleUpDataplane } = require('../commands/up-dataplane');
19
19
  const {
20
20
  applyUpPlatformForceConfig,
21
21
  cleanBuilderAppDirs,
22
+ PLATFORM_BUILDER_CLEAN_APP_KEYS,
22
23
  prepareUrlsLocalRegistryForUpPlatform
23
24
  } = require('../commands/up-common');
24
25
  const { runGuidedUpPlatform } = require('./infra-guided');
@@ -35,7 +36,7 @@ async function runUpPlatformWork(options) {
35
36
  let platformForceCleanSummary = null;
36
37
  if (options.force) {
37
38
  const forceSummary = await applyUpPlatformForceConfig({ silent: !options.verbose });
38
- const cleanedApps = await cleanBuilderAppDirs(PLATFORM_APPS, {
39
+ const cleanedApps = await cleanBuilderAppDirs(PLATFORM_BUILDER_CLEAN_APP_KEYS, {
39
40
  silent: !options.verbose
40
41
  });
41
42
  cleanedAppKeys = Array.isArray(cleanedApps) ? cleanedApps : [];
@@ -0,0 +1,59 @@
1
+ /**
2
+ * CLI npm command setup (user-global registry auth).
3
+ *
4
+ * @fileoverview aifabrix npm config command registration
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const { handleCommandError } = require('../utils/cli-utils');
10
+ const { handleNpmConfig } = require('../commands/npm-config');
11
+
12
+ const NPM_CONFIG_HELP_AFTER = `
13
+ Writes @aifabrix scope registry auth to your user npm config (~/.npmrc by default).
14
+ Does not modify repo-level .npmrc files.
15
+
16
+ Token source (default): merged secrets key BASH_NPM_TOKEN (user or --shared store).
17
+ Docker/build: BASH_NPM_TOKEN is also mapped to NPM_TOKEN for aifabrix build.
18
+
19
+ Examples:
20
+ $ aifabrix secret set BASH_NPM_TOKEN "<token>" --shared
21
+ $ aifabrix npm config
22
+ $ aifabrix npm config --check
23
+ $ aifabrix npm config --dry-run
24
+ $ NPM_TOKEN="<token>" aifabrix npm config --from-env
25
+
26
+ Migration from repo .npmrc.token:
27
+ 1. aifabrix secret remove npm-token-secretKeyVault --shared
28
+ 2. aifabrix secret set BASH_NPM_TOKEN "<token>" --shared
29
+ 3. aifabrix npm config
30
+ 4. npm whoami --registry=https://registry.npmjs.org/
31
+ `;
32
+
33
+ /**
34
+ * @param {import('commander').Command} program
35
+ */
36
+ function setupNpmCommands(program) {
37
+ const npm = program.command('npm').description('User-global npm registry configuration');
38
+
39
+ npm
40
+ .command('config')
41
+ .description('Apply @aifabrix npm auth from BASH_NPM_TOKEN to user ~/.npmrc')
42
+ .option('--dry-run', 'Show what would be written without changing files')
43
+ .option('--check', 'Exit 0 when BASH_NPM_TOKEN is set and npm whoami succeeds')
44
+ .option('--from-env', 'Use NPM_TOKEN from the environment once (not merged secrets)')
45
+ .addHelpText('after', NPM_CONFIG_HELP_AFTER)
46
+ .action(async(opts) => {
47
+ try {
48
+ await handleNpmConfig({
49
+ dryRun: Boolean(opts.dryRun),
50
+ check: Boolean(opts.check),
51
+ fromEnv: Boolean(opts.fromEnv)
52
+ });
53
+ } catch (error) {
54
+ handleCommandError(error, 'npm config');
55
+ }
56
+ });
57
+ }
58
+
59
+ module.exports = { setupNpmCommands };
@@ -54,7 +54,7 @@ function registerSetup(program) {
54
54
  .command('setup')
55
55
  .description('Install or refresh the full AI Fabrix platform (infra + miso + dataplane)')
56
56
  .option('-d, --developer <id>', 'Pin developer ID before fresh install (fresh path only; ignored when infra is already up)')
57
- .option('--platform <mode>', 'Platform mode: single (direct ports) or full (Traefik front door + multi-env)')
57
+ .option('--platform <mode>', 'Platform mode: single, single-traefik (Traefik + TLS), or full (multi-env; advanced)')
58
58
  .option('--installation <profile>', 'Installation profile: dev (one password) or pro (autogen/manual)')
59
59
  .option('--pro-password-mode <mode>', 'Pro profile only: autogen or manual')
60
60
  .option('--admin-email <email>', 'Admin email (non-interactive fresh install)')
@@ -101,7 +101,7 @@ Examples:
101
101
  const SECRET_SET_SECRETS_FILE_HELP_AFTER = `
102
102
  Examples:
103
103
  $ aifabrix secret set-secrets-file ./shared-secrets.yaml
104
- $ aifabrix secret set-secrets-file https://dev.example.com/api/secrets
104
+ $ aifabrix secret set-secrets-file https://builder01.local/api/dev/secrets
105
105
  $ aifabrix secret set-secrets-file ""
106
106
  `;
107
107
 
@@ -46,8 +46,11 @@ Examples:
46
46
  $ aifabrix resolve hubspot-demo
47
47
  External integration: resolves integration/<systemKey>/env.template to .env using local host ports (no Docker rewrite).
48
48
 
49
+ $ aifabrix resolve dataplane --force
50
+ Seed missing secret keys and replace the full .env from env.template (no merge with an existing file).
51
+
49
52
  $ aifabrix resolve dataplane --fresh
50
- Regenerate the full .env from env.template (no merge with an existing file). Use after adding many new variables or when a normal resolve did not pick up new template lines.
53
+ Replace the full .env only (no merge; does not seed missing secret keys). Same .env output as --force when secrets are already set.
51
54
 
52
55
  $ aifabrix secret set hubspot-demo/token "<your-token>"
53
56
  Store a secret when resolve reports a missing kv:// reference.
@@ -60,8 +60,8 @@ async function runResolveJsonOutput(appName, appPath, envOnly, envPath, options,
60
60
  */
61
61
  async function runResolveHumanOutput(appName, envPath, envOnly, options, environment) {
62
62
  logger.log(`✔ Generated .env file: ${envPath}`);
63
- if (options.fresh) {
64
- logger.log(chalk.gray(' Wrote full resolved content (--fresh; did not merge with an existing .env).'));
63
+ if (options.fresh || options.force) {
64
+ logger.log(chalk.gray(' Wrote full resolved content (replaced existing .env from env.template; no merge).'));
65
65
  }
66
66
  if (environment === 'local') {
67
67
  logger.log(chalk.gray(' Resolved for local use (host ports; no Docker port rewrite).'));
@@ -130,7 +130,7 @@ async function runResolveAppAction(appName, options) {
130
130
  skipOutputPath: false,
131
131
  preserveFromPath: null,
132
132
  preferLocalEnvOutputPath,
133
- freshEnv: options.fresh === true
133
+ freshEnv: options.fresh === true || options.force === true
134
134
  }
135
135
  );
136
136
  if (jsonMode) {
@@ -148,10 +148,13 @@ function setupResolveCommand(program) {
148
148
  program.command('resolve <appKey|systemKey>')
149
149
  .description('Generate .env from template (builder/<appKey>/ or integration/<systemKey>/); optional validate after')
150
150
  .addHelpText('after', RESOLVE_HELP_AFTER)
151
- .option('-f, --force', 'Generate missing secret keys in secrets file')
151
+ .option(
152
+ '-f, --force',
153
+ 'Seed missing secret keys and replace .env from env.template (no merge with existing .env or envOutputPath)'
154
+ )
152
155
  .option(
153
156
  '--fresh',
154
- 'Replace .env from env.template (no merge with existing .env or envOutputPath; same as deleting .env first)'
157
+ 'Same full .env replace as --force without seeding missing secret keys in the secrets file'
155
158
  )
156
159
  .option('--skip-validation', 'Skip file validation after generating .env')
157
160
  .option('--json', 'Print JSON summary (envPath, manifestSource, optional validation)')
@@ -13,6 +13,10 @@ const logger = require('../utils/logger');
13
13
  const config = require('../core/config');
14
14
  const containerHelpers = require('../utils/app-run-containers');
15
15
  const { validateAppName } = require('../app/push');
16
+ const {
17
+ getCeleryCompanionAppKeys,
18
+ loadBuilderApplicationConfig
19
+ } = require('../utils/celery-companion-apps');
16
20
 
17
21
  const { execWithDockerEnv } = require('../utils/docker-exec');
18
22
  const { maskEnvLine } = require('../utils/log-redaction');
@@ -295,6 +299,36 @@ async function runDockerLogsFollow(containerName, tail, minLevel) {
295
299
  });
296
300
  }
297
301
 
302
+ function parseLogLevelOption(options) {
303
+ const rawLevel =
304
+ options.level !== undefined && options.level !== null && options.level !== ''
305
+ ? String(options.level).trim()
306
+ : undefined;
307
+ const level = rawLevel ? rawLevel.toLowerCase() : undefined;
308
+ if (level !== undefined && level !== null && !LOG_LEVELS.includes(level)) {
309
+ throw new Error(
310
+ `Invalid log level '${rawLevel}'; use one of: ${LOG_LEVELS.join(', ')}`
311
+ );
312
+ }
313
+ return level;
314
+ }
315
+
316
+ async function logFollowStream(companionKeys, containerName, tail, level) {
317
+ await runDockerLogsFollow(containerName, tail, level);
318
+ if (companionKeys.length > 0) {
319
+ const hint = companionKeys.map((key) => `aifabrix logs ${key}`).join(' or ');
320
+ logger.log(chalk.gray(`Tip: sidecar logs — ${hint}`));
321
+ }
322
+ }
323
+
324
+ async function logContainerSnapshot(containerName, tail, level) {
325
+ try {
326
+ await runDockerLogs(containerName, { tail, level });
327
+ } catch (err) {
328
+ logger.log(chalk.yellow(`No logs for ${containerName}: ${err.message}`));
329
+ }
330
+ }
331
+
298
332
  /**
299
333
  * Run app logs command: optional env dump (masked), then docker logs
300
334
  * @async
@@ -307,39 +341,33 @@ async function runDockerLogsFollow(containerName, tail, minLevel) {
307
341
  */
308
342
  async function runAppLogs(appKey, options = {}) {
309
343
  validateAppName(appKey);
310
- const rawLevel =
311
- options.level !== undefined && options.level !== null && options.level !== ''
312
- ? String(options.level).trim()
313
- : undefined;
314
- const level = rawLevel ? rawLevel.toLowerCase() : undefined;
315
- if (level !== undefined && level !== null && !LOG_LEVELS.includes(level)) {
316
- throw new Error(
317
- `Invalid log level '${rawLevel}'; use one of: ${LOG_LEVELS.join(', ')}`
318
- );
319
- }
320
-
344
+ const level = parseLogLevelOption(options);
321
345
  const developerId = await config.getDeveloperId();
322
- const containerName = containerHelpers.getContainerName(appKey, developerId);
323
-
324
346
  const follow = !!options.follow;
325
347
  const tail = typeof options.tail === 'number' ? options.tail : DEFAULT_TAIL_LINES;
348
+ const appConfig = loadBuilderApplicationConfig(appKey);
349
+ const companionKeys = getCeleryCompanionAppKeys(appKey, appConfig);
350
+ const appKeysToLog = follow ? [appKey] : [appKey, ...companionKeys];
326
351
 
327
- logger.log(chalk.blue(`Container: ${containerName}\n`));
352
+ for (const key of appKeysToLog) {
353
+ const containerName = containerHelpers.getContainerName(key, developerId);
328
354
 
329
- if (!follow) {
330
- await dumpMaskedEnv(containerName);
331
- }
355
+ logger.log(chalk.blue(`Container: ${containerName}\n`));
332
356
 
333
- if (follow) {
334
- await runDockerLogsFollow(containerName, tail, level);
335
- return;
336
- }
357
+ if (!follow) {
358
+ await dumpMaskedEnv(containerName);
359
+ }
337
360
 
338
- try {
339
- await runDockerLogs(containerName, { tail, level });
340
- } catch (err) {
341
- logger.log(chalk.red(`Error: ${err.message}`));
342
- throw new Error(`Failed to show logs: ${err.message}`);
361
+ if (follow) {
362
+ await logFollowStream(companionKeys, containerName, tail, level);
363
+ return;
364
+ }
365
+
366
+ await logContainerSnapshot(containerName, tail, level);
367
+
368
+ if (key !== appKeysToLog[appKeysToLog.length - 1]) {
369
+ logger.log('');
370
+ }
343
371
  }
344
372
  }
345
373
 
@@ -17,6 +17,8 @@ const { registerApplication } = require('../app/register');
17
17
  const { rotateSecret } = require('../app/rotate-secret');
18
18
  const { showApp } = require('../app/show');
19
19
  const { runAppDeploymentList } = require('./deployment-list');
20
+ const { isChannelTargetKey } = require('../channels/channel-targets');
21
+ const { addChannelCommand } = require('../channels/add-channel');
20
22
 
21
23
  const APP_HELP_AFTER = `
22
24
  Subcommands:
@@ -25,6 +27,7 @@ Subcommands:
25
27
  rotate-secret <key> New ClientSecret (shown once)
26
28
  show <key> From controller (like show --online)
27
29
  deployment <key> Deployment history for app
30
+ add-channel <target> Configure assistant channel install (after deploy)
28
31
 
29
32
  Requires: aifabrix login
30
33
  `;
@@ -118,6 +121,43 @@ function setupAppDeploymentCommand(app) {
118
121
  });
119
122
  }
120
123
 
124
+ function setupAppAddChannelCommand(app) {
125
+ app.command('add-channel <channelTarget>')
126
+ .description(
127
+ 'Configure assistant channel install (teams-bot, slack, …) after aifabrix deploy <target>'
128
+ )
129
+ .requiredOption('-n, --name <name>', 'Customer-visible channel name')
130
+ .option('-d, --description <text>', 'Channel description (default generated)')
131
+ .option('--icon <path>', 'Path to channel icon asset')
132
+ .option('--template <name>', 'Template override (default from channel target)')
133
+ .option('--dry-run', 'Generate artifacts only; do not patch external system or credentials')
134
+ .option('--force', 'Regenerate artifacts and patch config (does not rotate secrets)')
135
+ .option('--probe', 'After install, validate callbacks and assistant catalog')
136
+ .action(async(channelTarget, options) => {
137
+ try {
138
+ if (!isChannelTargetKey(channelTarget)) {
139
+ throw new Error(
140
+ `Unknown channel target "${channelTarget}". Use teams-bot, slack, microsoft-copilot, or chatgpt.`
141
+ );
142
+ }
143
+ await addChannelCommand(channelTarget, {
144
+ name: options.name,
145
+ description: options.description,
146
+ icon: options.icon,
147
+ template: options.template,
148
+ dryRun: options.dryRun === true,
149
+ force: options.force === true,
150
+ probe: options.probe === true
151
+ });
152
+ } catch (error) {
153
+ if (!error.message?.includes('Authentication required')) {
154
+ logger.error(formatBlockingError('Channel install failed:'), error.message);
155
+ }
156
+ process.exit(1);
157
+ }
158
+ });
159
+ }
160
+
121
161
  /**
122
162
  * Setup application management commands
123
163
  * @param {Command} program - Commander program instance
@@ -129,6 +169,7 @@ function setupAppCommands(program) {
129
169
  setupAppRotateSecretCommand(app);
130
170
  setupAppShowCommand(app);
131
171
  setupAppDeploymentCommand(app);
172
+ setupAppAddChannelCommand(app);
132
173
  }
133
174
 
134
175
  module.exports = { setupAppCommands };
@@ -17,7 +17,8 @@
17
17
  const { fetchDataplaneGeneralHealth } = require('../api/dataplane-health.api');
18
18
  const {
19
19
  updateDeviceDataplaneVersions,
20
- getDeviceDataplaneVersions
20
+ getDeviceDataplaneVersions,
21
+ isCacheStale
21
22
  } = require('../core/config-device-dataplane');
22
23
  const {
23
24
  getInstalledCliVersion
@@ -25,10 +26,10 @@ const {
25
26
  const { isAtLeast, isValidSemver } = require('../utils/semver-compare');
26
27
 
27
28
  /** Max wall time for post-login version cache refresh (silent after success banner). */
28
- const LOGIN_POST_REFRESH_BUDGET_MS = 12000;
29
+ const LOGIN_POST_REFRESH_BUDGET_MS = 8000;
29
30
 
30
- /** Per health probe during login refresh (two endpoints may be tried). */
31
- const LOGIN_HEALTH_PROBE_TIMEOUT_MS = 6000;
31
+ /** Per health probe during login refresh (legacy /health first, then v1). */
32
+ const LOGIN_HEALTH_PROBE_TIMEOUT_MS = 3000;
32
33
 
33
34
  /**
34
35
  * Resolve when `promise` settles or when `timeoutMs` elapses (returns undefined).
@@ -79,29 +80,32 @@ async function safeReadCache(controllerUrl) {
79
80
  }
80
81
 
81
82
  /**
82
- * Refresh dataplane health, persist `device.<controllerUrl>` version cache,
83
- * and derive the compatibility verdict against the installed Builder CLI.
84
- * Falls back to the cached entry when the live probe fails so `auth status`
85
- * still shows the last-known values offline.
86
- *
87
- * @async
88
- * @param {string} controllerUrl - Controller URL (cache key)
89
- * @param {string} dataplaneUrl - Dataplane base URL for the health probe
90
- * @returns {Promise<{
91
- * dataplaneVersion: string|undefined,
92
- * minBuilderCliVersion: string|undefined,
93
- * cliVersion: string,
94
- * compatible: boolean
95
- * }>}
83
+ * Build optional fetch args for {@link fetchDataplaneGeneralHealth}.
84
+ * @param {Object} [options]
85
+ * @returns {Object}
96
86
  */
97
- async function refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl, options = {}) {
98
- const cliVersion = getInstalledCliVersion();
99
- let dataplaneVersion;
100
- let minBuilderCliVersion;
87
+ function buildHealthFetchOptions(options = {}) {
101
88
  const healthOpts = {};
102
89
  if (options.healthTimeoutMs !== undefined && options.healthTimeoutMs !== null) {
103
90
  healthOpts.timeoutMs = options.healthTimeoutMs;
104
91
  }
92
+ if (options.endpointOrder) {
93
+ healthOpts.endpointOrder = options.endpointOrder;
94
+ }
95
+ return healthOpts;
96
+ }
97
+
98
+ /**
99
+ * Probe dataplane health and persist version fields when available.
100
+ * @async
101
+ * @param {string} controllerUrl
102
+ * @param {string} dataplaneUrl
103
+ * @param {Object} healthOpts
104
+ * @returns {Promise<{ dataplaneVersion: string|undefined, minBuilderCliVersion: string|undefined }>}
105
+ */
106
+ async function fetchAndPersistHealthSnapshot(controllerUrl, dataplaneUrl, healthOpts) {
107
+ let dataplaneVersion;
108
+ let minBuilderCliVersion;
105
109
 
106
110
  try {
107
111
  const snapshot = await fetchDataplaneGeneralHealth(dataplaneUrl, healthOpts);
@@ -121,14 +125,63 @@ async function refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl, options
121
125
  // ignore — fall through to cached values
122
126
  }
123
127
 
124
- if (!dataplaneVersion && !minBuilderCliVersion) {
125
- const cached = await safeReadCache(controllerUrl);
126
- if (cached) {
127
- dataplaneVersion = cached.version;
128
- minBuilderCliVersion = cached.minBuilderCliVersion;
129
- }
128
+ return { dataplaneVersion, minBuilderCliVersion };
129
+ }
130
+
131
+ /**
132
+ * Fill missing version fields from the device cache when live probe failed.
133
+ * @async
134
+ * @param {string} controllerUrl
135
+ * @param {string|undefined} dataplaneVersion
136
+ * @param {string|undefined} minBuilderCliVersion
137
+ * @returns {Promise<{ dataplaneVersion: string|undefined, minBuilderCliVersion: string|undefined }>}
138
+ */
139
+ async function resolveVersionFieldsFromCache(controllerUrl, dataplaneVersion, minBuilderCliVersion) {
140
+ if (dataplaneVersion || minBuilderCliVersion) {
141
+ return { dataplaneVersion, minBuilderCliVersion };
130
142
  }
131
143
 
144
+ const cached = await safeReadCache(controllerUrl);
145
+ if (!cached) {
146
+ return { dataplaneVersion, minBuilderCliVersion };
147
+ }
148
+
149
+ return {
150
+ dataplaneVersion: cached.version,
151
+ minBuilderCliVersion: cached.minBuilderCliVersion
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Refresh dataplane health, persist `device.<controllerUrl>` version cache,
157
+ * and derive the compatibility verdict against the installed Builder CLI.
158
+ * Falls back to the cached entry when the live probe fails so `auth status`
159
+ * still shows the last-known values offline.
160
+ *
161
+ * @async
162
+ * @param {string} controllerUrl - Controller URL (cache key)
163
+ * @param {string} dataplaneUrl - Dataplane base URL for the health probe
164
+ * @returns {Promise<{
165
+ * dataplaneVersion: string|undefined,
166
+ * minBuilderCliVersion: string|undefined,
167
+ * cliVersion: string,
168
+ * compatible: boolean
169
+ * }>}
170
+ */
171
+ async function refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl, options = {}) {
172
+ const cliVersion = getInstalledCliVersion();
173
+ const healthOpts = buildHealthFetchOptions(options);
174
+ let { dataplaneVersion, minBuilderCliVersion } = await fetchAndPersistHealthSnapshot(
175
+ controllerUrl,
176
+ dataplaneUrl,
177
+ healthOpts
178
+ );
179
+ ({ dataplaneVersion, minBuilderCliVersion } = await resolveVersionFieldsFromCache(
180
+ controllerUrl,
181
+ dataplaneVersion,
182
+ minBuilderCliVersion
183
+ ));
184
+
132
185
  const compatible = computeCompatibility(cliVersion, minBuilderCliVersion);
133
186
  return { dataplaneVersion, minBuilderCliVersion, cliVersion, compatible };
134
187
  }
@@ -184,33 +237,6 @@ async function resolveAuthConfigAfterLogin(controllerUrl, environment, loginOpti
184
237
  }
185
238
  }
186
239
 
187
- /**
188
- * Resolve dataplane URL after login (mirrors auth-status silent resolver).
189
- * @private
190
- * @async
191
- * @param {string} controllerUrl
192
- * @param {string} environment
193
- * @param {Object} authConfig
194
- * @returns {Promise<string|null>}
195
- */
196
- async function resolveDataplaneUrlAfterLogin(controllerUrl, environment, authConfig) {
197
- const { findDataplaneServiceAppKey } = require('./wizard-dataplane');
198
- const { getDataplaneUrl } = require('../datasource/deploy');
199
- try {
200
- const dataplaneAppKey = await findDataplaneServiceAppKey(
201
- controllerUrl,
202
- environment,
203
- authConfig
204
- );
205
- if (dataplaneAppKey) {
206
- return await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
207
- }
208
- return await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
209
- } catch (_resolveError) {
210
- return null;
211
- }
212
- }
213
-
214
240
  /**
215
241
  * Best-effort health refresh after login when dataplane URL is discoverable.
216
242
  * Never throws — cache failures must not block login success.
@@ -222,7 +248,13 @@ async function resolveDataplaneUrlAfterLogin(controllerUrl, environment, authCon
222
248
  * @returns {Promise<void>}
223
249
  */
224
250
  async function tryRefreshDataplaneVersionAfterLoginWork(controllerUrl, loginOptions = {}) {
251
+ const cached = await safeReadCache(controllerUrl);
252
+ if (cached && !isCacheStale(cached)) {
253
+ return;
254
+ }
255
+
225
256
  const { resolveEnvironment } = require('../core/config');
257
+ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
226
258
  const environment = await resolveEnvironment();
227
259
  const authConfig = await resolveAuthConfigAfterLogin(
228
260
  controllerUrl,
@@ -232,14 +264,18 @@ async function tryRefreshDataplaneVersionAfterLoginWork(controllerUrl, loginOpti
232
264
  if (!authConfig) {
233
265
  return;
234
266
  }
235
- const dataplaneUrl = await resolveDataplaneUrlAfterLogin(
236
- controllerUrl,
237
- environment,
238
- authConfig
239
- );
267
+ let dataplaneUrl = null;
268
+ try {
269
+ dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, {
270
+ silent: true
271
+ });
272
+ } catch (_resolveError) {
273
+ return;
274
+ }
240
275
  if (dataplaneUrl) {
241
276
  await refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl, {
242
- healthTimeoutMs: LOGIN_HEALTH_PROBE_TIMEOUT_MS
277
+ healthTimeoutMs: LOGIN_HEALTH_PROBE_TIMEOUT_MS,
278
+ endpointOrder: 'legacy-first'
243
279
  });
244
280
  }
245
281
  }
@@ -14,8 +14,7 @@ const logger = require('../utils/logger');
14
14
  const { getOrRefreshDeviceToken } = require('../utils/token-manager');
15
15
  const { getAuthUser } = require('../api/auth.api');
16
16
  const { resolveControllerUrl } = require('../utils/controller-url');
17
- const { findDataplaneServiceAppKey } = require('./wizard-dataplane');
18
- const { getDataplaneUrl } = require('../datasource/deploy');
17
+ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
19
18
  const { checkDataplaneHealth } = require('../utils/dataplane-health');
20
19
  const {
21
20
  displayDataplaneVersionSection,
@@ -211,11 +210,7 @@ function displayTokenInfo(tokenInfo) {
211
210
  */
212
211
  async function resolveDataplaneUrlSilent(controllerUrl, environment, authConfig) {
213
212
  try {
214
- const dataplaneAppKey = await findDataplaneServiceAppKey(controllerUrl, environment, authConfig);
215
- if (dataplaneAppKey) {
216
- return await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
217
- }
218
- return await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
213
+ return await resolveDataplaneUrl(controllerUrl, environment, authConfig, { silent: true });
219
214
  } catch {
220
215
  return null;
221
216
  }