@aifabrix/builder 2.45.5 → 2.47.0

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 (355) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/.npmrc.token +1 -0
  5. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  7. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  8. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  9. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  10. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  14. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  15. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  16. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  17. package/.temp/validation/summary.txt +5 -0
  18. package/README.md +43 -17
  19. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  20. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  21. package/jest.config.default.js +2 -1
  22. package/jest.config.isolated.js +2 -1
  23. package/jest.global-live-fabrix-hooks.js +36 -0
  24. package/jest.isolated-projects.js +146 -0
  25. package/jest.projects.js +65 -136
  26. package/lib/api/api-response-errors.js +124 -0
  27. package/lib/api/dataplane-health.api.js +19 -7
  28. package/lib/api/governance-scenario-pack.api.js +159 -0
  29. package/lib/api/index.js +43 -3
  30. package/lib/api/lifecycle.api.js +98 -0
  31. package/lib/api/pipeline.api.js +3 -1
  32. package/lib/api/records-bulk.api.js +14 -1
  33. package/lib/api/resource-types.api.js +107 -0
  34. package/lib/api/runtime-configuration.api.js +72 -0
  35. package/lib/api/types/resource-types.types.js +45 -0
  36. package/lib/api/user-groups.api.js +9 -9
  37. package/lib/api/users.api.js +8 -8
  38. package/lib/api/validation-runner.js +3 -1
  39. package/lib/api/wizard.api.js +10 -2
  40. package/lib/app/deploy-config.js +4 -1
  41. package/lib/app/down.js +78 -27
  42. package/lib/app/index.js +2 -2
  43. package/lib/app/push.js +4 -5
  44. package/lib/app/register.js +7 -4
  45. package/lib/app/run-env-compose.js +84 -30
  46. package/lib/app/run-resolve-image.js +2 -1
  47. package/lib/app/run-stop-containers.js +113 -0
  48. package/lib/app/run.js +1 -31
  49. package/lib/app/show-display.js +35 -0
  50. package/lib/app/show.js +32 -1
  51. package/lib/build/index.js +22 -5
  52. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  53. package/lib/certification/sync-system-certification.js +13 -6
  54. package/lib/channels/add-channel.js +348 -0
  55. package/lib/channels/channel-artifact-generator.js +141 -0
  56. package/lib/channels/channel-audit.js +101 -0
  57. package/lib/channels/channel-auth-context.js +37 -0
  58. package/lib/channels/channel-credential.js +200 -0
  59. package/lib/channels/channel-display-name.js +64 -0
  60. package/lib/channels/channel-external-system.js +106 -0
  61. package/lib/channels/channel-integration-generator.js +274 -0
  62. package/lib/channels/channel-probe.js +89 -0
  63. package/lib/channels/channel-runtime-resolver.js +152 -0
  64. package/lib/channels/channel-status-display.js +141 -0
  65. package/lib/channels/channel-targets.js +115 -0
  66. package/lib/cli/index.js +20 -4
  67. package/lib/cli/infra-guided-auth.js +109 -0
  68. package/lib/cli/infra-guided.js +84 -81
  69. package/lib/cli/setup-app.help.js +0 -31
  70. package/lib/cli/setup-app.js +82 -5
  71. package/lib/cli/setup-app.test-commands.js +5 -57
  72. package/lib/cli/setup-auth.js +25 -0
  73. package/lib/cli/setup-certification-commands.js +147 -0
  74. package/lib/cli/setup-certification.help.js +141 -0
  75. package/lib/cli/setup-environment.js +19 -2
  76. package/lib/cli/setup-external-system-help.js +62 -0
  77. package/lib/cli/setup-external-system.js +25 -3
  78. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  79. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  80. package/lib/cli/setup-infra.js +9 -1
  81. package/lib/cli/setup-platform.js +6 -1
  82. package/lib/cli/setup-secrets.js +33 -1
  83. package/lib/cli/setup-utility-help-after.js +25 -2
  84. package/lib/cli/setup-utility-repair.js +198 -0
  85. package/lib/cli/setup-utility-resolve.js +44 -5
  86. package/lib/cli/setup-utility-validate.js +197 -0
  87. package/lib/cli/setup-utility.js +4 -239
  88. package/lib/commands/app-logs.js +54 -26
  89. package/lib/commands/app.js +41 -0
  90. package/lib/commands/auth-status-client-tokens.js +214 -0
  91. package/lib/commands/auth-status-dataplane-version.js +142 -68
  92. package/lib/commands/auth-status.js +43 -54
  93. package/lib/commands/channel.js +58 -0
  94. package/lib/commands/datasource-test-trust-cli.js +27 -23
  95. package/lib/commands/datasource-unified-test-cli.js +6 -3
  96. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  97. package/lib/commands/datasource.js +8 -8
  98. package/lib/commands/dev-show-display.js +8 -1
  99. package/lib/commands/governance-cli-shared.js +94 -0
  100. package/lib/commands/governance-command-action.js +132 -0
  101. package/lib/commands/governance-download-external.js +34 -0
  102. package/lib/commands/governance-generate-external.js +71 -0
  103. package/lib/commands/governance-upload-external.js +36 -0
  104. package/lib/commands/governance-verify-external.js +64 -0
  105. package/lib/commands/governance.js +144 -0
  106. package/lib/commands/identity-apply-sync.js +49 -8
  107. package/lib/commands/identity-group-cli.js +66 -26
  108. package/lib/commands/identity-membership-role-cache.js +58 -37
  109. package/lib/commands/identity-shared.js +22 -1
  110. package/lib/commands/identity-user-cli.js +80 -34
  111. package/lib/commands/lifecycle-command-action.js +123 -0
  112. package/lib/commands/lifecycle-external.js +268 -0
  113. package/lib/commands/login.js +11 -2
  114. package/lib/commands/repair-auth-apply.js +153 -0
  115. package/lib/commands/repair-auth-config.js +8 -13
  116. package/lib/commands/repair-changed-files.js +41 -0
  117. package/lib/commands/repair-datasource-com-v2.js +15 -0
  118. package/lib/commands/repair-datasource-keys.js +4 -1
  119. package/lib/commands/repair-datasource-run.js +59 -0
  120. package/lib/commands/repair-datasource-service-contract.js +206 -0
  121. package/lib/commands/repair-datasource-test-payload.js +259 -0
  122. package/lib/commands/repair-datasource.js +49 -64
  123. package/lib/commands/repair-env-template.js +76 -25
  124. package/lib/commands/repair-external-run.js +216 -0
  125. package/lib/commands/repair-openapi-sync.js +49 -4
  126. package/lib/commands/repair-persist.js +4 -0
  127. package/lib/commands/repair-rbac.js +52 -1
  128. package/lib/commands/repair-steps.js +160 -0
  129. package/lib/commands/repair-system-alignment.js +7 -4
  130. package/lib/commands/repair.js +29 -296
  131. package/lib/commands/resource-type.js +371 -0
  132. package/lib/commands/secrets-get.js +184 -0
  133. package/lib/commands/secrets-remove-all.js +3 -1
  134. package/lib/commands/setup-image-refresh.js +146 -0
  135. package/lib/commands/setup-modes.js +158 -162
  136. package/lib/commands/setup-platform-auth.js +76 -0
  137. package/lib/commands/setup-prompts-admin.js +244 -0
  138. package/lib/commands/setup-prompts-platform-mode.js +70 -0
  139. package/lib/commands/setup-prompts-validators.js +36 -0
  140. package/lib/commands/setup-prompts.js +24 -76
  141. package/lib/commands/setup-run-context.js +45 -0
  142. package/lib/commands/setup-spinners.js +39 -0
  143. package/lib/commands/setup.js +27 -5
  144. package/lib/commands/teardown.js +39 -0
  145. package/lib/commands/test-e2e-external.js +24 -8
  146. package/lib/commands/test-governance-external.js +3 -24
  147. package/lib/commands/test-trust-external.js +48 -100
  148. package/lib/commands/up-common.js +57 -13
  149. package/lib/commands/up-dataplane.js +56 -15
  150. package/lib/commands/upload-publish-context.js +75 -0
  151. package/lib/commands/upload-sidecar-followups.js +89 -0
  152. package/lib/commands/upload.js +113 -110
  153. package/lib/commands/verify-governance-combined-external.js +61 -0
  154. package/lib/commands/verify-governance-command-action.js +64 -0
  155. package/lib/commands/verify-governance-db.js +102 -0
  156. package/lib/commands/verify-governance-external.js +77 -0
  157. package/lib/commands/verify-governance-result-builders.js +71 -0
  158. package/lib/commands/verify-operations-command-action.js +68 -0
  159. package/lib/commands/verify-operations-external.js +268 -0
  160. package/lib/commands/verify-operations-persist.js +75 -0
  161. package/lib/commands/verify-operations-step-plan.js +68 -0
  162. package/lib/commands/verify-operations-steps.js +111 -0
  163. package/lib/commands/verify-trust-command-action.js +73 -0
  164. package/lib/commands/verify-trust-external.js +59 -0
  165. package/lib/commands/wizard-config-normalizer.js +43 -23
  166. package/lib/commands/wizard-core.js +2 -0
  167. package/lib/core/admin-password-generator.js +38 -0
  168. package/lib/core/admin-secrets-bundle.js +111 -0
  169. package/lib/core/admin-secrets-env-overlay.js +62 -0
  170. package/lib/core/admin-secrets.js +32 -4
  171. package/lib/core/config-attach-extensions.js +3 -0
  172. package/lib/core/config-platform-controller.js +44 -0
  173. package/lib/core/config-setup-installation-profile.js +42 -0
  174. package/lib/core/config-setup-platform-mode.js +46 -0
  175. package/lib/core/config.js +46 -36
  176. package/lib/core/secrets-admin-env.js +6 -2
  177. package/lib/core/secrets-ensure.js +4 -0
  178. package/lib/core/secrets-env-content.js +54 -48
  179. package/lib/core/secrets-env-write.js +10 -1
  180. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  181. package/lib/core/setup-installation-profile.js +43 -0
  182. package/lib/core/setup-platform-mode.js +90 -0
  183. package/lib/datasource/agent-trust-debug-log.js +1 -1
  184. package/lib/datasource/agent-trust-run.js +5 -3
  185. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  186. package/lib/datasource/audit-evidence-extract.js +7 -4
  187. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  188. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  189. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  190. package/lib/datasource/capability/capability-manifest.js +254 -0
  191. package/lib/datasource/capability/capability-resolve.js +3 -4
  192. package/lib/datasource/capability/copy-operations.js +5 -3
  193. package/lib/datasource/capability/remove-operations.js +2 -1
  194. package/lib/datasource/capability/run-capability-copy.js +32 -4
  195. package/lib/datasource/capability/run-capability-diff.js +3 -3
  196. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  197. package/lib/datasource/capability/run-capability-edit.js +4 -7
  198. package/lib/datasource/capability/run-capability-relate.js +2 -4
  199. package/lib/datasource/capability/run-capability-remove.js +2 -4
  200. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  201. package/lib/datasource/datasource-identifier.js +2 -1
  202. package/lib/datasource/datasource-load-export-context.js +3 -0
  203. package/lib/datasource/datasource-validate-display.js +1 -1
  204. package/lib/datasource/field-reference-validator.js +24 -3
  205. package/lib/datasource/log-cleaner.js +2 -2
  206. package/lib/datasource/log-viewer.js +8 -8
  207. package/lib/datasource/orphan-profiles-validator.js +98 -0
  208. package/lib/datasource/test-e2e.js +4 -1
  209. package/lib/datasource/unified-validation-run-body.js +3 -1
  210. package/lib/datasource/unified-validation-run.js +5 -2
  211. package/lib/datasource/validate.js +41 -23
  212. package/lib/deployment/environment-poll-status.js +138 -0
  213. package/lib/deployment/environment.js +42 -34
  214. package/lib/external-system/download-helpers.js +87 -0
  215. package/lib/external-system/download.js +78 -138
  216. package/lib/external-system/generator.js +1 -55
  217. package/lib/external-system/integration-auth-context.js +45 -0
  218. package/lib/external-system/test-auth.js +12 -5
  219. package/lib/external-system/test.js +16 -21
  220. package/lib/generator/builders.js +11 -1
  221. package/lib/generator/external-controller-manifest.js +18 -0
  222. package/lib/generator/split.js +3 -1
  223. package/lib/generator/wizard.js +1 -28
  224. package/lib/governance/governance-cli-errors.js +96 -0
  225. package/lib/governance/governance-download-display.js +38 -0
  226. package/lib/governance/governance-run-display.js +215 -25
  227. package/lib/identity/fk-reference.js +65 -0
  228. package/lib/identity/identity-apply-core.js +13 -58
  229. package/lib/identity/identity-apply-phases.js +5 -1
  230. package/lib/identity/identity-display.js +243 -0
  231. package/lib/identity/identity-response-helpers.js +52 -0
  232. package/lib/identity/identity-sync-errors.js +62 -0
  233. package/lib/identity/resolve-user-id.js +129 -0
  234. package/lib/identity/username-default.js +31 -0
  235. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  236. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  237. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  238. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  239. package/lib/infrastructure/helpers.js +23 -129
  240. package/lib/infrastructure/index.js +5 -6
  241. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  242. package/lib/lifecycle/certification-progress.js +231 -0
  243. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  244. package/lib/lifecycle/exit-codes.js +24 -0
  245. package/lib/lifecycle/json-envelope.js +25 -0
  246. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  247. package/lib/lifecycle/logger-quiet.js +29 -0
  248. package/lib/lifecycle/open-api-published.js +59 -0
  249. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  250. package/lib/lifecycle/product-model.js +196 -0
  251. package/lib/lifecycle/report-display-trust.js +70 -0
  252. package/lib/lifecycle/report-display.js +472 -0
  253. package/lib/lifecycle/scenario-upload.js +191 -0
  254. package/lib/lifecycle/verify-step-progress.js +87 -0
  255. package/lib/parameters/infra-kv-discovery.js +27 -2
  256. package/lib/protection/auth-context.js +10 -4
  257. package/lib/resolvers/datasource-resolver.js +2 -3
  258. package/lib/resolvers/resource-type-file.js +159 -0
  259. package/lib/schema/application-schema.json +17 -1
  260. package/lib/schema/datasource-test-run.schema.json +89 -6
  261. package/lib/schema/deployment-rules.yaml +2 -1
  262. package/lib/schema/external-datasource.schema.json +2947 -2619
  263. package/lib/schema/external-system.schema.json +1418 -761
  264. package/lib/schema/infra.parameter.yaml +45 -0
  265. package/lib/schema/type/credential-field-registry.json +103 -97
  266. package/lib/schema/type/document-storage.json +9 -5
  267. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  268. package/lib/utils/api.js +55 -31
  269. package/lib/utils/app-relative-path-env.js +112 -0
  270. package/lib/utils/app-run-containers.js +25 -2
  271. package/lib/utils/authentication-shape.js +224 -0
  272. package/lib/utils/build-copy.js +36 -5
  273. package/lib/utils/build-resolve-image.js +66 -12
  274. package/lib/utils/celery-companion-apps.js +53 -0
  275. package/lib/utils/cli-secrets-error-format.js +83 -21
  276. package/lib/utils/cli-utils.js +20 -4
  277. package/lib/utils/compose-celery-worker.js +23 -0
  278. package/lib/utils/compose-generate-docker-compose.js +13 -1
  279. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  280. package/lib/utils/config-secrets-path.js +46 -0
  281. package/lib/utils/configuration-env-resolver.js +36 -0
  282. package/lib/utils/controller-url.js +88 -1
  283. package/lib/utils/credential-secrets-env-kv.js +216 -0
  284. package/lib/utils/credential-secrets-env.js +175 -257
  285. package/lib/utils/dataplane-resolver.js +18 -2
  286. package/lib/utils/declarative-url-ports.js +27 -4
  287. package/lib/utils/deployment-auth-mode.js +41 -0
  288. package/lib/utils/developer-volume-id.js +33 -0
  289. package/lib/utils/docker-build.js +1 -4
  290. package/lib/utils/env-copy.js +14 -9
  291. package/lib/utils/env-kv-orphan-lines.js +43 -0
  292. package/lib/utils/error-formatters/network-errors.js +13 -4
  293. package/lib/utils/external-env-template.js +11 -3
  294. package/lib/utils/external-system-readiness-core.js +10 -0
  295. package/lib/utils/external-system-readiness-display.js +80 -23
  296. package/lib/utils/external-system-validators.js +8 -2
  297. package/lib/utils/health-check.js +4 -1
  298. package/lib/utils/help-builder.js +13 -8
  299. package/lib/utils/keycloak-realm-ready.js +101 -0
  300. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  301. package/lib/utils/manifest-source-emit.js +2 -0
  302. package/lib/utils/paths.js +25 -0
  303. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  304. package/lib/utils/platform-controller-url.js +157 -0
  305. package/lib/utils/platform-kv-ref.js +57 -0
  306. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  307. package/lib/utils/postgres-wipe.js +9 -2
  308. package/lib/utils/resolve-env-mode.js +47 -0
  309. package/lib/utils/runtime-configuration-download.js +189 -0
  310. package/lib/utils/runtime-configuration-values.js +119 -0
  311. package/lib/utils/schema-loader.js +19 -4
  312. package/lib/utils/secrets-generator.js +31 -25
  313. package/lib/utils/secrets-missing-error.js +164 -0
  314. package/lib/utils/service-url-label.js +81 -0
  315. package/lib/utils/setup-quiet-output.js +25 -0
  316. package/lib/utils/template-helpers.js +15 -0
  317. package/lib/utils/token-manager-client-credentials.js +201 -0
  318. package/lib/utils/token-manager-messages.js +8 -0
  319. package/lib/utils/token-manager-refresh.js +12 -2
  320. package/lib/utils/token-manager.js +86 -105
  321. package/lib/utils/upload-pipeline-ui.js +12 -5
  322. package/lib/utils/upload-sync-options.js +42 -0
  323. package/lib/utils/url-declarative-public-base.js +21 -3
  324. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  325. package/lib/utils/validation-poll-ui.js +8 -0
  326. package/lib/utils/validation-run-stage.js +45 -0
  327. package/lib/utils/worker-sidecars.js +142 -0
  328. package/lib/validation/external-manifest-validator.js +2 -0
  329. package/lib/validation/runtime-configuration-rules.js +98 -0
  330. package/lib/validation/validate-display-dimensions.js +154 -0
  331. package/lib/validation/validate-display-sections.js +276 -0
  332. package/lib/validation/validate-display-steps.js +67 -0
  333. package/lib/validation/validate-display.js +24 -402
  334. package/package.json +1 -1
  335. package/templates/applications/dataplane/application.yaml +7 -1
  336. package/templates/applications/dataplane/env.template +50 -14
  337. package/templates/applications/dataplane/rbac.yaml +108 -1
  338. package/templates/applications/miso-controller/application.yaml +2 -2
  339. package/templates/applications/miso-controller/env.template +53 -122
  340. package/templates/applications/miso-controller/rbac.yaml +28 -13
  341. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  342. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  343. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  344. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  345. package/templates/channels/slack/install.template.md.hbs +49 -0
  346. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  347. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  348. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  349. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  350. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  351. package/templates/external-system/README.md.hbs +74 -17
  352. package/templates/python/docker-compose.hbs +75 -18
  353. package/templates/typescript/docker-compose.hbs +70 -17
  354. package/lib/commands/test-governance-command-action.js +0 -55
  355. package/lib/commands/test-trust-command-action.js +0 -118
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Client token listing and proactive refresh for auth status.
3
+ *
4
+ * @fileoverview Stored app tokens, auto-refresh when credentials exist
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const config = require('../core/config');
13
+ const logger = require('../utils/logger');
14
+ const { successGlyph, failureGlyph } = require('../utils/cli-test-layout-chalk');
15
+ const {
16
+ getOrRefreshClientToken,
17
+ loadClientCredentials,
18
+ refreshClientToken,
19
+ isTokenExpired
20
+ } = require('../utils/token-manager');
21
+
22
+ /**
23
+ * @param {string} controllerUrl
24
+ * @param {string} storedController
25
+ * @returns {boolean}
26
+ */
27
+ function controllersMatch(controllerUrl, storedController) {
28
+ if (!storedController) {
29
+ return false;
30
+ }
31
+ return config.normalizeControllerUrl(storedController) === config.normalizeControllerUrl(controllerUrl);
32
+ }
33
+
34
+ /**
35
+ * @param {string} environment
36
+ * @param {string} controllerUrl
37
+ * @returns {Promise<Record<string, { controller?: string, token?: string, expiresAt?: string }>>}
38
+ */
39
+ async function getClientEntriesForController(environment, controllerUrl) {
40
+ const configData = await config.getConfig();
41
+ const clients = configData.environments?.[environment]?.clients || {};
42
+ const out = {};
43
+ for (const [appName, tokenData] of Object.entries(clients)) {
44
+ if (controllersMatch(controllerUrl, tokenData?.controller)) {
45
+ out[appName] = tokenData;
46
+ }
47
+ }
48
+ return out;
49
+ }
50
+
51
+ /**
52
+ * Refresh expired client tokens when secrets.local.yaml (or env) has client id/secret.
53
+ *
54
+ * @async
55
+ * @param {string} environment
56
+ * @param {string} controllerUrl
57
+ * @returns {Promise<number>} Count refreshed
58
+ */
59
+ async function refreshExpiredClientTokensWherePossible(environment, controllerUrl) {
60
+ const entries = await getClientEntriesForController(environment, controllerUrl);
61
+ let refreshed = 0;
62
+ for (const appName of Object.keys(entries)) {
63
+ const tokenData = entries[appName];
64
+ if (!tokenData?.token || !isTokenExpired(tokenData.expiresAt)) {
65
+ continue;
66
+ }
67
+ const credentials = await loadClientCredentials(appName);
68
+ if (!credentials?.clientId || !credentials?.clientSecret) {
69
+ continue;
70
+ }
71
+ try {
72
+ await refreshClientToken(environment, appName, controllerUrl);
73
+ refreshed += 1;
74
+ } catch {
75
+ // Skip apps without valid credentials or controller errors
76
+ }
77
+ }
78
+ return refreshed;
79
+ }
80
+
81
+ /**
82
+ * @param {string} expiresAt
83
+ * @returns {'valid'|'expired'|'unknown'}
84
+ */
85
+ function tokenExpiryState(expiresAt) {
86
+ if (!expiresAt) {
87
+ return 'unknown';
88
+ }
89
+ return isTokenExpired(expiresAt) ? 'expired' : 'valid';
90
+ }
91
+
92
+ /**
93
+ * Build rows for stored client tokens table.
94
+ *
95
+ * @async
96
+ * @param {string} environment
97
+ * @param {string} controllerUrl
98
+ * @returns {Promise<Array<{ appName: string, expiresAt: string, state: string, logoutCmd: string }>>}
99
+ */
100
+ async function listClientTokenRows(environment, controllerUrl) {
101
+ const entries = await getClientEntriesForController(environment, controllerUrl);
102
+ const rows = [];
103
+ for (const appName of Object.keys(entries).sort()) {
104
+ const tokenData = entries[appName];
105
+ const state = tokenExpiryState(tokenData.expiresAt);
106
+ const credentials = await loadClientCredentials(appName);
107
+ const canRefresh = !!(credentials?.clientId && credentials?.clientSecret);
108
+ let stateLabel = state;
109
+ if (state === 'expired' && canRefresh) {
110
+ stateLabel = 'expired (credentials available — refreshed on status if possible)';
111
+ } else if (state === 'expired' && !canRefresh) {
112
+ stateLabel = 'expired (no client id/secret in secrets — logout or login)';
113
+ }
114
+ rows.push({
115
+ appName,
116
+ expiresAt: tokenData.expiresAt || '—',
117
+ state: stateLabel,
118
+ logoutCmd: `aifabrix logout -e ${environment} -a ${appName}`
119
+ });
120
+ }
121
+ return rows;
122
+ }
123
+
124
+ /**
125
+ * Print stored application client tokens and logout commands.
126
+ *
127
+ * @async
128
+ * @param {string} environment
129
+ * @param {string} controllerUrl
130
+ * @param {number} [refreshedCount]
131
+ * @returns {Promise<void>}
132
+ */
133
+ async function displayStoredClientTokensSection(environment, controllerUrl, refreshedCount = 0) {
134
+ const rows = await listClientTokenRows(environment, controllerUrl);
135
+ if (rows.length === 0) {
136
+ return;
137
+ }
138
+
139
+ logger.log('');
140
+ logger.log(chalk.bold('Stored application client tokens:'));
141
+ if (refreshedCount > 0) {
142
+ logger.log(
143
+ chalk.gray(
144
+ ` Refreshed ${refreshedCount} expired token(s) using client id/secret from secrets.local.yaml or env.`
145
+ )
146
+ );
147
+ }
148
+ logger.log(chalk.gray(` Environment: ${environment} · Controller: ${controllerUrl}`));
149
+ logger.log('');
150
+ for (const row of rows) {
151
+ const icon = row.state.startsWith('valid') ? successGlyph() : failureGlyph();
152
+ logger.log(` ${icon} ${chalk.cyan(row.appName)}`);
153
+ logger.log(` Expires: ${chalk.gray(row.expiresAt)}`);
154
+ logger.log(` Status: ${row.state}`);
155
+ logger.log(` Clear: ${chalk.gray(row.logoutCmd)}`);
156
+ }
157
+ logger.log('');
158
+ logger.log(chalk.gray(' Clear all client tokens for this environment:'), chalk.gray(`aifabrix logout -e ${environment}`));
159
+ logger.log(chalk.gray(' Clear device login for this controller:'), chalk.gray(`aifabrix logout -c ${controllerUrl}`));
160
+ logger.log('');
161
+ }
162
+
163
+ /**
164
+ * Resolve a working client token (refresh when expired if credentials exist).
165
+ *
166
+ * @async
167
+ * @param {string} controllerUrl
168
+ * @param {string} environment
169
+ * @param {function(string, string, string, string, string): Promise<Object>} validateClientTokenFn
170
+ * @returns {Promise<Object|null>}
171
+ */
172
+ async function tryResolveClientTokenAuth(controllerUrl, environment, validateClientTokenFn) {
173
+ const entries = await getClientEntriesForController(environment, controllerUrl);
174
+ const appNames = Object.keys(entries);
175
+ if (appNames.length === 0) {
176
+ return null;
177
+ }
178
+
179
+ const ordered = ['dataplane', ...appNames.filter(a => a !== 'dataplane').sort()];
180
+ let lastFailure = null;
181
+
182
+ for (const appName of ordered) {
183
+ try {
184
+ const refreshed = await getOrRefreshClientToken(environment, appName, controllerUrl);
185
+ if (!refreshed?.token) {
186
+ continue;
187
+ }
188
+ const stored = await config.getClientToken(environment, appName);
189
+ const result = await validateClientTokenFn(
190
+ refreshed.token,
191
+ controllerUrl,
192
+ environment,
193
+ appName,
194
+ stored?.expiresAt
195
+ );
196
+ if (result.authenticated) {
197
+ return result;
198
+ }
199
+ lastFailure = result;
200
+ } catch {
201
+ // Try next app
202
+ }
203
+ }
204
+
205
+ return lastFailure;
206
+ }
207
+
208
+ module.exports = {
209
+ refreshExpiredClientTokensWherePossible,
210
+ displayStoredClientTokensSection,
211
+ tryResolveClientTokenAuth,
212
+ listClientTokenRows,
213
+ getClientEntriesForController
214
+ };
@@ -17,13 +17,36 @@
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
24
25
  } = require('../utils/dataplane-cli-version-gate');
25
26
  const { isAtLeast, isValidSemver } = require('../utils/semver-compare');
26
27
 
28
+ /** Max wall time for post-login version cache refresh (silent after success banner). */
29
+ const LOGIN_POST_REFRESH_BUDGET_MS = 8000;
30
+
31
+ /** Per health probe during login refresh (legacy /health first, then v1). */
32
+ const LOGIN_HEALTH_PROBE_TIMEOUT_MS = 3000;
33
+
34
+ /**
35
+ * Resolve when `promise` settles or when `timeoutMs` elapses (returns undefined).
36
+ * @template T
37
+ * @param {Promise<T>} promise
38
+ * @param {number} timeoutMs
39
+ * @returns {Promise<T|undefined>}
40
+ */
41
+ function raceWithTimeout(promise, timeoutMs) {
42
+ return Promise.race([
43
+ promise,
44
+ new Promise((resolve) => {
45
+ setTimeout(() => resolve(undefined), timeoutMs);
46
+ })
47
+ ]);
48
+ }
49
+
27
50
  /**
28
51
  * Per plan 142.0: invalid semver from the dataplane is logged but does **not**
29
52
  * block the CLI; missing min version means no enforcement.
@@ -57,28 +80,35 @@ async function safeReadCache(controllerUrl) {
57
80
  }
58
81
 
59
82
  /**
60
- * Refresh dataplane health, persist `device.<controllerUrl>` version cache,
61
- * and derive the compatibility verdict against the installed Builder CLI.
62
- * Falls back to the cached entry when the live probe fails so `auth status`
63
- * still shows the last-known values offline.
64
- *
83
+ * Build optional fetch args for {@link fetchDataplaneGeneralHealth}.
84
+ * @param {Object} [options]
85
+ * @returns {Object}
86
+ */
87
+ function buildHealthFetchOptions(options = {}) {
88
+ const healthOpts = {};
89
+ if (options.healthTimeoutMs !== undefined && options.healthTimeoutMs !== null) {
90
+ healthOpts.timeoutMs = options.healthTimeoutMs;
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.
65
100
  * @async
66
- * @param {string} controllerUrl - Controller URL (cache key)
67
- * @param {string} dataplaneUrl - Dataplane base URL for the health probe
68
- * @returns {Promise<{
69
- * dataplaneVersion: string|undefined,
70
- * minBuilderCliVersion: string|undefined,
71
- * cliVersion: string,
72
- * compatible: boolean
73
- * }>}
101
+ * @param {string} controllerUrl
102
+ * @param {string} dataplaneUrl
103
+ * @param {Object} healthOpts
104
+ * @returns {Promise<{ dataplaneVersion: string|undefined, minBuilderCliVersion: string|undefined }>}
74
105
  */
75
- async function refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl) {
76
- const cliVersion = getInstalledCliVersion();
106
+ async function fetchAndPersistHealthSnapshot(controllerUrl, dataplaneUrl, healthOpts) {
77
107
  let dataplaneVersion;
78
108
  let minBuilderCliVersion;
79
109
 
80
110
  try {
81
- const snapshot = await fetchDataplaneGeneralHealth(dataplaneUrl);
111
+ const snapshot = await fetchDataplaneGeneralHealth(dataplaneUrl, healthOpts);
82
112
  if (snapshot) {
83
113
  dataplaneVersion = snapshot.version;
84
114
  minBuilderCliVersion = snapshot.minBuilderCliVersion;
@@ -95,14 +125,63 @@ async function refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl) {
95
125
  // ignore — fall through to cached values
96
126
  }
97
127
 
98
- if (!dataplaneVersion && !minBuilderCliVersion) {
99
- const cached = await safeReadCache(controllerUrl);
100
- if (cached) {
101
- dataplaneVersion = cached.version;
102
- minBuilderCliVersion = cached.minBuilderCliVersion;
103
- }
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 };
104
142
  }
105
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
+
106
185
  const compatible = computeCompatibility(cliVersion, minBuilderCliVersion);
107
186
  return { dataplaneVersion, minBuilderCliVersion, cliVersion, compatible };
108
187
  }
@@ -158,33 +237,6 @@ async function resolveAuthConfigAfterLogin(controllerUrl, environment, loginOpti
158
237
  }
159
238
  }
160
239
 
161
- /**
162
- * Resolve dataplane URL after login (mirrors auth-status silent resolver).
163
- * @private
164
- * @async
165
- * @param {string} controllerUrl
166
- * @param {string} environment
167
- * @param {Object} authConfig
168
- * @returns {Promise<string|null>}
169
- */
170
- async function resolveDataplaneUrlAfterLogin(controllerUrl, environment, authConfig) {
171
- const { findDataplaneServiceAppKey } = require('./wizard-dataplane');
172
- const { getDataplaneUrl } = require('../datasource/deploy');
173
- try {
174
- const dataplaneAppKey = await findDataplaneServiceAppKey(
175
- controllerUrl,
176
- environment,
177
- authConfig
178
- );
179
- if (dataplaneAppKey) {
180
- return await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
181
- }
182
- return await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
183
- } catch (_resolveError) {
184
- return null;
185
- }
186
- }
187
-
188
240
  /**
189
241
  * Best-effort health refresh after login when dataplane URL is discoverable.
190
242
  * Never throws — cache failures must not block login success.
@@ -195,29 +247,48 @@ async function resolveDataplaneUrlAfterLogin(controllerUrl, environment, authCon
195
247
  * @param {string} [loginOptions.app] - App name (credentials login) for client-token auth
196
248
  * @returns {Promise<void>}
197
249
  */
250
+ async function tryRefreshDataplaneVersionAfterLoginWork(controllerUrl, loginOptions = {}) {
251
+ const cached = await safeReadCache(controllerUrl);
252
+ if (cached && !isCacheStale(cached)) {
253
+ return;
254
+ }
255
+
256
+ const { resolveEnvironment } = require('../core/config');
257
+ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
258
+ const environment = await resolveEnvironment();
259
+ const authConfig = await resolveAuthConfigAfterLogin(
260
+ controllerUrl,
261
+ environment,
262
+ loginOptions
263
+ );
264
+ if (!authConfig) {
265
+ return;
266
+ }
267
+ let dataplaneUrl = null;
268
+ try {
269
+ dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, {
270
+ silent: true
271
+ });
272
+ } catch (_resolveError) {
273
+ return;
274
+ }
275
+ if (dataplaneUrl) {
276
+ await refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl, {
277
+ healthTimeoutMs: LOGIN_HEALTH_PROBE_TIMEOUT_MS,
278
+ endpointOrder: 'legacy-first'
279
+ });
280
+ }
281
+ }
282
+
198
283
  async function tryRefreshDataplaneVersionAfterLogin(controllerUrl, loginOptions = {}) {
199
284
  if (!controllerUrl || typeof controllerUrl !== 'string') {
200
285
  return;
201
286
  }
202
287
  try {
203
- const { resolveEnvironment } = require('../core/config');
204
- const environment = await resolveEnvironment();
205
- const authConfig = await resolveAuthConfigAfterLogin(
206
- controllerUrl,
207
- environment,
208
- loginOptions
288
+ await raceWithTimeout(
289
+ tryRefreshDataplaneVersionAfterLoginWork(controllerUrl, loginOptions),
290
+ LOGIN_POST_REFRESH_BUDGET_MS
209
291
  );
210
- if (!authConfig) {
211
- return;
212
- }
213
- const dataplaneUrl = await resolveDataplaneUrlAfterLogin(
214
- controllerUrl,
215
- environment,
216
- authConfig
217
- );
218
- if (dataplaneUrl) {
219
- await refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl);
220
- }
221
292
  } catch (_error) {
222
293
  // best-effort only
223
294
  }
@@ -227,5 +298,8 @@ module.exports = {
227
298
  refreshDataplaneVersionInfo,
228
299
  loadCachedVersionInfo,
229
300
  computeCompatibility,
230
- tryRefreshDataplaneVersionAfterLogin
301
+ tryRefreshDataplaneVersionAfterLogin,
302
+ LOGIN_POST_REFRESH_BUDGET_MS,
303
+ LOGIN_HEALTH_PROBE_TIMEOUT_MS,
304
+ raceWithTimeout
231
305
  };
@@ -11,13 +11,10 @@
11
11
  const chalk = require('chalk');
12
12
  const { failureGlyph, successGlyph } = require('../utils/cli-test-layout-chalk');
13
13
  const logger = require('../utils/logger');
14
- const config = require('../core/config');
15
- const { getConfig } = config;
16
14
  const { getOrRefreshDeviceToken } = require('../utils/token-manager');
17
15
  const { getAuthUser } = require('../api/auth.api');
18
16
  const { resolveControllerUrl } = require('../utils/controller-url');
19
- const { findDataplaneServiceAppKey } = require('./wizard-dataplane');
20
- const { getDataplaneUrl } = require('../datasource/deploy');
17
+ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
21
18
  const { checkDataplaneHealth } = require('../utils/dataplane-health');
22
19
  const {
23
20
  displayDataplaneVersionSection,
@@ -32,6 +29,11 @@ const {
32
29
  EXIT_CLI_VERSION_INCOMPATIBLE,
33
30
  EXIT_OK
34
31
  } = require('../utils/cli-exit-codes');
32
+ const {
33
+ refreshExpiredClientTokensWherePossible,
34
+ displayStoredClientTokensSection,
35
+ tryResolveClientTokenAuth
36
+ } = require('./auth-status-client-tokens');
35
37
 
36
38
  /**
37
39
  * Format expiration date for display
@@ -94,22 +96,6 @@ async function checkDeviceToken(controllerUrl) {
94
96
  }
95
97
  }
96
98
 
97
- /**
98
- * Decrypt token if encrypted
99
- * @async
100
- * @param {string} token - Token to decrypt
101
- * @returns {Promise<string>} Decrypted token
102
- */
103
- async function decryptTokenIfNeeded(token) {
104
- const { decryptToken, isTokenEncrypted } = require('../utils/token-encryption');
105
- const encryptionKey = await config.getSecretsEncryptionKey();
106
-
107
- if (encryptionKey && isTokenEncrypted(token)) {
108
- return await decryptToken(token, encryptionKey);
109
- }
110
- return token;
111
- }
112
-
113
99
  /**
114
100
  * Validate client token and return result
115
101
  * @async
@@ -156,29 +142,14 @@ async function validateClientToken(token, controllerUrl, environment, appName, e
156
142
  }
157
143
 
158
144
  /**
159
- * Check and validate client token
145
+ * Check and validate client token (refreshes when expired if client credentials exist).
160
146
  * @async
161
147
  * @param {string} controllerUrl - Controller URL
162
148
  * @param {string} environment - Environment key
163
149
  * @returns {Promise<Object|null>} Token validation result or null
164
150
  */
165
151
  async function checkClientToken(controllerUrl, environment) {
166
- const configData = await getConfig();
167
- const environments = configData.environments || {};
168
- const envConfig = environments[environment];
169
-
170
- if (!envConfig || !envConfig.clients) {
171
- return null;
172
- }
173
-
174
- for (const [appName, tokenData] of Object.entries(envConfig.clients)) {
175
- if (tokenData.controller === controllerUrl && tokenData.token) {
176
- const token = await decryptTokenIfNeeded(tokenData.token);
177
- return await validateClientToken(token, controllerUrl, environment, appName, tokenData.expiresAt);
178
- }
179
- }
180
-
181
- return null;
152
+ return tryResolveClientTokenAuth(controllerUrl, environment, validateClientToken);
182
153
  }
183
154
 
184
155
  /**
@@ -239,11 +210,7 @@ function displayTokenInfo(tokenInfo) {
239
210
  */
240
211
  async function resolveDataplaneUrlSilent(controllerUrl, environment, authConfig) {
241
212
  try {
242
- const dataplaneAppKey = await findDataplaneServiceAppKey(controllerUrl, environment, authConfig);
243
- if (dataplaneAppKey) {
244
- return await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
245
- }
246
- return await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
213
+ return await resolveDataplaneUrl(controllerUrl, environment, authConfig, { silent: true });
247
214
  } catch {
248
215
  return null;
249
216
  }
@@ -382,18 +349,34 @@ async function buildDataplaneInfo(controllerUrl, environment, authConfig) {
382
349
  * @param {boolean} [options.validate] - If true, exit with code 1 when not authenticated, or code 3 when CLI is older than dataplane minimum
383
350
  * @returns {Promise<void>} Resolves when status is displayed
384
351
  */
352
+ /**
353
+ * Prefer a working device token; fall back to environment client credentials when the
354
+ * stored device token is expired or rejected (common in long-lived dev VMs).
355
+ *
356
+ * @async
357
+ * @param {string} controllerUrl
358
+ * @param {string} environment
359
+ * @returns {Promise<Object|null>}
360
+ */
361
+ async function resolveAuthenticatedTokenInfo(controllerUrl, environment) {
362
+ const deviceResult = await checkDeviceToken(controllerUrl);
363
+ if (deviceResult && deviceResult.authenticated) {
364
+ return deviceResult;
365
+ }
366
+ const clientResult = await checkClientToken(controllerUrl, environment);
367
+ if (clientResult && clientResult.authenticated) {
368
+ return clientResult;
369
+ }
370
+ return deviceResult || clientResult || null;
371
+ }
372
+
385
373
  async function handleAuthStatus(options = {}) {
386
374
  const { resolveEnvironment } = require('../core/config');
387
375
  const controllerUrl = await resolveControllerUrl();
388
376
  const environment = await resolveEnvironment();
389
377
 
390
- // Check device token first (preferred)
391
- let tokenInfo = await checkDeviceToken(controllerUrl);
392
-
393
- // If no device token, check client token
394
- if (!tokenInfo) {
395
- tokenInfo = await checkClientToken(controllerUrl, environment);
396
- }
378
+ const refreshedCount = await refreshExpiredClientTokensWherePossible(environment, controllerUrl);
379
+ const tokenInfo = await resolveAuthenticatedTokenInfo(controllerUrl, environment);
397
380
 
398
381
  let dataplaneInfo = null;
399
382
  if (tokenInfo && tokenInfo.authenticated && tokenInfo.token) {
@@ -402,6 +385,7 @@ async function handleAuthStatus(options = {}) {
402
385
  }
403
386
 
404
387
  displayStatus(controllerUrl, environment, tokenInfo, dataplaneInfo);
388
+ await displayStoredClientTokensSection(environment, controllerUrl, refreshedCount);
405
389
 
406
390
  if (options.validate) {
407
391
  if (!tokenInfo || !tokenInfo.authenticated) {
@@ -427,10 +411,7 @@ async function getValidatedAuthContext() {
427
411
  const controllerUrl = await resolveControllerUrl();
428
412
  const environment = await resolveEnvironment();
429
413
 
430
- let tokenInfo = await checkDeviceToken(controllerUrl);
431
- if (!tokenInfo) {
432
- tokenInfo = await checkClientToken(controllerUrl, environment);
433
- }
414
+ const tokenInfo = await resolveAuthenticatedTokenInfo(controllerUrl, environment);
434
415
 
435
416
  if (!tokenInfo || !tokenInfo.authenticated || !tokenInfo.token) {
436
417
  throw new Error('Not authenticated. Run "aifabrix login" or configure client credentials.');
@@ -442,4 +423,12 @@ async function getValidatedAuthContext() {
442
423
  return { controllerUrl, environment, authConfig, dataplaneUrl };
443
424
  }
444
425
 
445
- module.exports = { handleAuthStatus, getValidatedAuthContext };
426
+ module.exports = {
427
+ handleAuthStatus,
428
+ getValidatedAuthContext,
429
+ resolveAuthenticatedTokenInfo,
430
+ checkDeviceToken,
431
+ checkClientToken,
432
+ refreshExpiredClientTokensWherePossible,
433
+ displayStoredClientTokensSection
434
+ };