@aifabrix/builder 2.43.0 → 2.44.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 (346) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +1 -1
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +31 -0
  7. package/integration/hubspot-test/create-hubspot.js +5 -5
  8. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  9. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  10. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  11. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  12. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  13. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  14. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  15. package/integration/hubspot-test/test.js +35 -43
  16. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  17. package/integration/roundtrip-test-local/README.md +144 -0
  18. package/integration/roundtrip-test-local/application.yaml +13 -0
  19. package/integration/roundtrip-test-local/env.template +15 -0
  20. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  21. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  22. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  23. package/integration/roundtrip-test-local2/README.md +144 -0
  24. package/integration/roundtrip-test-local2/application.yaml +13 -0
  25. package/integration/roundtrip-test-local2/env.template +15 -0
  26. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  27. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  28. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  29. package/integration/test/wizard.yaml +8 -0
  30. package/jest.config.default.js +10 -0
  31. package/jest.config.integration.fixtures.js +22 -0
  32. package/jest.config.integration.js +21 -18
  33. package/jest.config.isolated.js +10 -0
  34. package/jest.projects.js +288 -0
  35. package/lib/api/datasources-core.api.js +3 -3
  36. package/lib/api/dev-mtls-request.js +110 -0
  37. package/lib/api/dev-server-https.js +145 -0
  38. package/lib/api/dev.api.js +133 -144
  39. package/lib/api/index.js +0 -1
  40. package/lib/api/pipeline.api.js +67 -20
  41. package/lib/api/types/dev.types.js +4 -3
  42. package/lib/api/types/pipeline.types.js +8 -5
  43. package/lib/api/types/validation-run.types.js +56 -0
  44. package/lib/api/validation-run.api.js +99 -0
  45. package/lib/api/validation-runner.js +99 -0
  46. package/lib/app/config.js +1 -1
  47. package/lib/app/deploy-status-display.js +2 -2
  48. package/lib/app/deploy.js +7 -6
  49. package/lib/app/display.js +2 -1
  50. package/lib/app/dockerfile.js +3 -2
  51. package/lib/app/down.js +2 -1
  52. package/lib/app/helpers.js +6 -5
  53. package/lib/app/index.js +27 -8
  54. package/lib/app/list.js +7 -6
  55. package/lib/app/push.js +4 -3
  56. package/lib/app/register.js +16 -7
  57. package/lib/app/rotate-secret.js +14 -13
  58. package/lib/app/run-container-start.js +184 -0
  59. package/lib/app/run-docker-fallback.js +108 -0
  60. package/lib/app/run-env-compose.js +30 -42
  61. package/lib/app/run-helpers.js +49 -126
  62. package/lib/app/run-infra-requirements.js +30 -0
  63. package/lib/app/run-resolve-image.js +21 -0
  64. package/lib/app/run.js +74 -21
  65. package/lib/app/show-display.js +1 -1
  66. package/lib/app/show.js +1 -1
  67. package/lib/build/index.js +13 -10
  68. package/lib/cli/index.js +2 -0
  69. package/lib/cli/setup-app.help.js +67 -0
  70. package/lib/cli/setup-app.js +57 -121
  71. package/lib/cli/setup-app.test-commands.js +179 -0
  72. package/lib/cli/setup-auth.js +19 -5
  73. package/lib/cli/setup-credential-deployment.js +22 -8
  74. package/lib/cli/setup-dev-path-commands.js +124 -0
  75. package/lib/cli/setup-dev.js +170 -113
  76. package/lib/cli/setup-environment.js +7 -1
  77. package/lib/cli/setup-external-system.js +62 -22
  78. package/lib/cli/setup-infra.js +126 -47
  79. package/lib/cli/setup-parameters.js +32 -0
  80. package/lib/cli/setup-secrets.js +106 -8
  81. package/lib/cli/setup-service-user.js +1 -1
  82. package/lib/cli/setup-utility.js +36 -20
  83. package/lib/commands/app-down.js +5 -7
  84. package/lib/commands/app-install.js +14 -7
  85. package/lib/commands/app-logs.js +13 -10
  86. package/lib/commands/app-shell.js +4 -1
  87. package/lib/commands/app-test.js +25 -19
  88. package/lib/commands/app.js +22 -10
  89. package/lib/commands/auth-config.js +6 -6
  90. package/lib/commands/auth-status.js +4 -3
  91. package/lib/commands/credential-env.js +4 -3
  92. package/lib/commands/credential-list.js +5 -4
  93. package/lib/commands/credential-push.js +4 -3
  94. package/lib/commands/datasource-unified-test-cli.js +495 -0
  95. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  96. package/lib/commands/datasource-validation-cli.js +129 -0
  97. package/lib/commands/datasource.js +105 -98
  98. package/lib/commands/deployment-list.js +6 -5
  99. package/lib/commands/dev-cli-handlers.js +122 -18
  100. package/lib/commands/dev-down.js +4 -3
  101. package/lib/commands/dev-init.js +231 -116
  102. package/lib/commands/dev-show-display.js +473 -0
  103. package/lib/commands/login-credentials.js +3 -2
  104. package/lib/commands/login-device.js +4 -3
  105. package/lib/commands/login.js +5 -4
  106. package/lib/commands/logout.js +8 -7
  107. package/lib/commands/parameters-validate.js +54 -0
  108. package/lib/commands/repair-datasource.js +314 -68
  109. package/lib/commands/repair-env-template.js +2 -2
  110. package/lib/commands/repair.js +21 -3
  111. package/lib/commands/secrets-list.js +23 -12
  112. package/lib/commands/secrets-remove-all.js +220 -0
  113. package/lib/commands/secrets-remove.js +21 -12
  114. package/lib/commands/secrets-set.js +21 -12
  115. package/lib/commands/secrets-validate.js +4 -4
  116. package/lib/commands/secure.js +10 -9
  117. package/lib/commands/service-user.js +26 -25
  118. package/lib/commands/test-e2e-external.js +27 -1
  119. package/lib/commands/up-common.js +3 -2
  120. package/lib/commands/up-dataplane.js +29 -16
  121. package/lib/commands/up-miso.js +19 -29
  122. package/lib/commands/upload.js +138 -39
  123. package/lib/commands/wizard-core-helpers.js +1 -1
  124. package/lib/commands/wizard-dataplane.js +4 -3
  125. package/lib/commands/wizard-helpers.js +3 -3
  126. package/lib/commands/wizard.js +2 -2
  127. package/lib/core/admin-secrets.js +14 -5
  128. package/lib/core/audit-logger.js +12 -4
  129. package/lib/core/config-attach-extensions.js +46 -0
  130. package/lib/core/config-runtime-paths.js +29 -0
  131. package/lib/core/config.js +55 -56
  132. package/lib/core/diff.js +3 -2
  133. package/lib/core/ensure-encryption-key.js +1 -1
  134. package/lib/core/secrets-ensure-infra.js +77 -0
  135. package/lib/core/secrets-ensure.js +120 -64
  136. package/lib/core/secrets-env-write.js +35 -7
  137. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  138. package/lib/core/secrets.js +200 -37
  139. package/lib/core/templates-env.js +4 -3
  140. package/lib/datasource/abac-validator.js +1 -10
  141. package/lib/datasource/deploy.js +75 -53
  142. package/lib/datasource/field-reference-validator.js +9 -6
  143. package/lib/datasource/integration-context.js +63 -0
  144. package/lib/datasource/list.js +8 -7
  145. package/lib/datasource/log-viewer.js +84 -53
  146. package/lib/datasource/resolve-app.js +4 -4
  147. package/lib/datasource/test-e2e.js +95 -146
  148. package/lib/datasource/test-integration.js +114 -122
  149. package/lib/datasource/unified-validation-run-body.js +65 -0
  150. package/lib/datasource/unified-validation-run-post.js +23 -0
  151. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  152. package/lib/datasource/unified-validation-run.js +92 -0
  153. package/lib/datasource/validate.js +157 -13
  154. package/lib/deployment/deployer.js +4 -3
  155. package/lib/deployment/environment.js +7 -6
  156. package/lib/deployment/push.js +17 -8
  157. package/lib/external-system/delete.js +4 -3
  158. package/lib/external-system/deploy.js +131 -53
  159. package/lib/external-system/download-helpers.js +1 -1
  160. package/lib/external-system/download.js +7 -6
  161. package/lib/external-system/generator.js +92 -6
  162. package/lib/external-system/integration-test-dispatch.js +26 -0
  163. package/lib/external-system/test-execution.js +5 -1
  164. package/lib/external-system/test-helpers.js +0 -4
  165. package/lib/external-system/test-system-level-helpers.js +110 -0
  166. package/lib/external-system/test-system-level.js +83 -44
  167. package/lib/external-system/test.js +59 -8
  168. package/lib/generator/builders.js +23 -11
  169. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  170. package/lib/generator/external.js +16 -4
  171. package/lib/generator/helpers.js +58 -3
  172. package/lib/generator/index.js +4 -0
  173. package/lib/generator/split-readme.js +12 -7
  174. package/lib/generator/split-variables.js +2 -1
  175. package/lib/generator/split.js +1 -1
  176. package/lib/generator/wizard-readme.js +3 -3
  177. package/lib/generator/wizard.js +8 -8
  178. package/lib/infrastructure/compose.js +60 -6
  179. package/lib/infrastructure/helpers.js +201 -29
  180. package/lib/infrastructure/index.js +28 -17
  181. package/lib/infrastructure/services.js +21 -15
  182. package/lib/internal/fs-real-sync.js +104 -0
  183. package/lib/internal/node-fs.js +98 -0
  184. package/lib/parameters/database-secret-values.js +173 -0
  185. package/lib/parameters/infra-kv-discovery.js +121 -0
  186. package/lib/parameters/infra-parameter-catalog.js +458 -0
  187. package/lib/parameters/infra-parameter-validate.js +64 -0
  188. package/lib/schema/application-schema.json +37 -17
  189. package/lib/schema/datasource-test-run.schema.json +493 -0
  190. package/lib/schema/deployment-rules.yaml +102 -63
  191. package/lib/schema/external-datasource.schema.json +1200 -442
  192. package/lib/schema/external-system.schema.json +181 -5
  193. package/lib/schema/flag-map-validation-run.json +31 -0
  194. package/lib/schema/infra-parameter.schema.json +106 -0
  195. package/lib/schema/infra.parameter.yaml +421 -0
  196. package/lib/schema/type/credential-auth-templates.json +40 -0
  197. package/lib/schema/type/document-storage.json +213 -0
  198. package/lib/schema/type/message-service.json +123 -0
  199. package/lib/schema/type/vector-store.json +88 -0
  200. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  201. package/lib/utils/api-error-handler.js +2 -2
  202. package/lib/utils/api.js +49 -14
  203. package/lib/utils/app-register-api.js +3 -2
  204. package/lib/utils/app-register-auth.js +1 -1
  205. package/lib/utils/app-register-config.js +4 -4
  206. package/lib/utils/app-register-display.js +3 -2
  207. package/lib/utils/app-register-validator.js +3 -2
  208. package/lib/utils/app-run-containers.js +26 -22
  209. package/lib/utils/app-scoped-config.js +31 -0
  210. package/lib/utils/app-service-env-from-builder.js +164 -0
  211. package/lib/utils/build-copy.js +1 -1
  212. package/lib/utils/build-helpers.js +20 -20
  213. package/lib/utils/build-resolve-image.js +165 -0
  214. package/lib/utils/cli-layout-chalk.js +8 -0
  215. package/lib/utils/cli-test-layout-chalk.js +267 -0
  216. package/lib/utils/cli-utils.js +88 -11
  217. package/lib/utils/compose-db-passwords.js +138 -0
  218. package/lib/utils/compose-generate-docker-compose.js +216 -0
  219. package/lib/utils/compose-generator.js +197 -291
  220. package/lib/utils/compose-miso-env.js +18 -0
  221. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  222. package/lib/utils/config-paths.js +166 -7
  223. package/lib/utils/config-scoped-resources-preference.js +41 -0
  224. package/lib/utils/controller-deployment-outcome.js +68 -0
  225. package/lib/utils/credential-display.js +2 -2
  226. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  227. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  228. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  229. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  230. package/lib/utils/datasource-test-run-display.js +442 -0
  231. package/lib/utils/datasource-test-run-exit.js +58 -0
  232. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  233. package/lib/utils/datasource-test-run-report-version.js +51 -0
  234. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  235. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  236. package/lib/utils/datasource-validation-watch.js +266 -0
  237. package/lib/utils/declarative-url-ports.js +47 -0
  238. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  239. package/lib/utils/dev-ca-install.js +185 -23
  240. package/lib/utils/dev-cert-helper.js +266 -17
  241. package/lib/utils/dev-hosts-helper.js +307 -0
  242. package/lib/utils/dev-init-cert-hints.js +37 -0
  243. package/lib/utils/dev-init-health-messages.js +52 -0
  244. package/lib/utils/dev-init-resolve.js +86 -0
  245. package/lib/utils/dev-init-ssh-merge.js +65 -0
  246. package/lib/utils/dev-ssh-config-helper.js +196 -0
  247. package/lib/utils/dev-user-groups.js +93 -0
  248. package/lib/utils/docker-build.js +42 -17
  249. package/lib/utils/docker-exec.js +28 -0
  250. package/lib/utils/docker-manifest-public-port.js +116 -0
  251. package/lib/utils/docker-not-running-hint.js +52 -0
  252. package/lib/utils/docker.js +98 -11
  253. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  254. package/lib/utils/env-config-loader.js +10 -91
  255. package/lib/utils/env-copy.js +19 -10
  256. package/lib/utils/env-map.js +35 -8
  257. package/lib/utils/env-template.js +2 -2
  258. package/lib/utils/environment-scoped-resources.js +144 -0
  259. package/lib/utils/error-formatter.js +92 -13
  260. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  261. package/lib/utils/error-formatters/network-errors.js +2 -1
  262. package/lib/utils/error-formatters/permission-errors.js +2 -1
  263. package/lib/utils/error-formatters/validation-errors.js +2 -1
  264. package/lib/utils/external-readme.js +8 -1
  265. package/lib/utils/external-system-display.js +234 -136
  266. package/lib/utils/external-system-local-test-tty.js +389 -0
  267. package/lib/utils/external-system-readiness-core.js +377 -0
  268. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  269. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  270. package/lib/utils/external-system-readiness-display.js +186 -0
  271. package/lib/utils/external-system-test-helpers.js +24 -6
  272. package/lib/utils/external-system-validators.js +30 -12
  273. package/lib/utils/health-check-url.js +119 -0
  274. package/lib/utils/health-check.js +59 -25
  275. package/lib/utils/help-builder.js +11 -8
  276. package/lib/utils/image-version.js +4 -8
  277. package/lib/utils/infra-containers.js +4 -7
  278. package/lib/utils/infra-env-defaults.js +162 -0
  279. package/lib/utils/infra-status-display.js +167 -0
  280. package/lib/utils/infra-status.js +16 -8
  281. package/lib/utils/local-secrets.js +3 -4
  282. package/lib/utils/paths.js +134 -47
  283. package/lib/utils/port-resolver.js +10 -23
  284. package/lib/utils/redis-env-scope.js +62 -0
  285. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  286. package/lib/utils/remote-builder-validation.js +99 -0
  287. package/lib/utils/remote-dev-auth.js +117 -21
  288. package/lib/utils/remote-docker-env.js +67 -15
  289. package/lib/utils/remote-secrets-loader.js +13 -4
  290. package/lib/utils/resolve-docker-image-ref.js +124 -0
  291. package/lib/utils/schema-loader.js +22 -9
  292. package/lib/utils/secrets-bash-kv.js +25 -0
  293. package/lib/utils/secrets-generator.js +169 -49
  294. package/lib/utils/secrets-helpers.js +70 -59
  295. package/lib/utils/secrets-kv-scope.js +60 -0
  296. package/lib/utils/secrets-utils.js +32 -38
  297. package/lib/utils/secrets-validation.js +3 -1
  298. package/lib/utils/secrets-yaml-preserve.js +109 -0
  299. package/lib/utils/ssh-key-helper.js +4 -2
  300. package/lib/utils/template-helpers.js +2 -2
  301. package/lib/utils/test-log-writer.js +3 -3
  302. package/lib/utils/token-manager.js +1 -2
  303. package/lib/utils/url-declarative-public-base.js +188 -0
  304. package/lib/utils/url-declarative-resolve-build.js +493 -0
  305. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  306. package/lib/utils/url-declarative-resolve.js +220 -0
  307. package/lib/utils/url-declarative-token-parse.js +74 -0
  308. package/lib/utils/url-declarative-url-flags.js +50 -0
  309. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  310. package/lib/utils/url-public-path-prefix.js +34 -0
  311. package/lib/utils/urls-local-registry.js +220 -0
  312. package/lib/utils/validation-report-tty-kit.js +77 -0
  313. package/lib/utils/validation-run-poll.js +89 -0
  314. package/lib/utils/validation-run-post-retry.js +73 -0
  315. package/lib/utils/validation-run-request.js +98 -0
  316. package/lib/utils/variable-transformer.js +21 -4
  317. package/lib/utils/yaml-preserve.js +33 -14
  318. package/lib/validation/datasource-warnings.js +56 -0
  319. package/lib/validation/env-template-auth.js +1 -1
  320. package/lib/validation/external-manifest-validator.js +27 -7
  321. package/lib/validation/validate-display.js +37 -31
  322. package/lib/validation/validate.js +4 -13
  323. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  324. package/lib/validation/validator.js +22 -65
  325. package/lib/validation/wizard-config-validator.js +2 -1
  326. package/package.json +7 -3
  327. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  328. package/scripts/diagnose-cli.js +150 -0
  329. package/scripts/install-local.js +304 -55
  330. package/templates/README.md +15 -2
  331. package/templates/applications/dataplane/application.yaml +52 -2
  332. package/templates/applications/dataplane/env.template +75 -17
  333. package/templates/applications/dataplane/rbac.yaml +8 -0
  334. package/templates/applications/keycloak/application.yaml +9 -1
  335. package/templates/applications/keycloak/env.template +15 -6
  336. package/templates/applications/miso-controller/application.yaml +10 -2
  337. package/templates/applications/miso-controller/env.template +42 -12
  338. package/templates/applications/miso-controller/rbac.yaml +5 -0
  339. package/templates/external-system/README.md.hbs +20 -7
  340. package/templates/external-system/deploy.js.hbs +5 -5
  341. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  342. package/templates/infra/compose.yaml.hbs +20 -4
  343. package/templates/python/docker-compose.hbs +16 -0
  344. package/templates/typescript/docker-compose.hbs +16 -0
  345. package/lib/api/external-test.api.js +0 -111
  346. package/lib/schema/env-config.yaml +0 -60
@@ -0,0 +1,220 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
2
+ /**
3
+ * @fileoverview aifabrix secret remove-all – remove every secret (user file, shared file, or remote API)
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const readline = require('readline');
11
+ const yaml = require('js-yaml');
12
+ const chalk = require('chalk');
13
+ const logger = require('../utils/logger');
14
+ const { getAifabrixSecretsPath } = require('../core/config');
15
+ const pathsUtil = require('../utils/paths');
16
+ const remoteDevAuth = require('../utils/remote-dev-auth');
17
+ const devApi = require('../api/dev.api');
18
+
19
+ /**
20
+ * @param {string} question
21
+ * @returns {Promise<string>}
22
+ */
23
+ function promptLine(question) {
24
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
25
+ return new Promise(resolve => {
26
+ rl.question(question, answer => {
27
+ rl.close();
28
+ resolve((answer || '').trim());
29
+ });
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Read secret keys from a YAML secrets file.
35
+ * @param {string} filePath - Absolute path
36
+ * @returns {string[]} Sorted unique keys
37
+ */
38
+ function listKeysFromYamlFile(filePath) {
39
+ if (!fs.existsSync(filePath)) {
40
+ return [];
41
+ }
42
+ try {
43
+ const content = fs.readFileSync(filePath, 'utf8');
44
+ const data = yaml.load(content) || {};
45
+ if (typeof data !== 'object' || Array.isArray(data)) {
46
+ return [];
47
+ }
48
+ return Object.keys(data).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
49
+ } catch {
50
+ return [];
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Write an empty secrets map to file (same options as single-key remove).
56
+ * @param {string} filePath - Absolute path
57
+ */
58
+ function writeEmptySecretsFile(filePath) {
59
+ const yamlContent = yaml.dump({}, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
60
+ fs.writeFileSync(filePath, yamlContent, { mode: 0o600 });
61
+ }
62
+
63
+ /**
64
+ * @param {boolean} skipPrompt - When true (--yes), skip confirmation
65
+ * @param {string} whereLabel - Human-readable target
66
+ * @param {number} count - Number of keys
67
+ * @returns {Promise<boolean>}
68
+ */
69
+ async function confirmRemoveAll(skipPrompt, whereLabel, count) {
70
+ if (skipPrompt) {
71
+ return true;
72
+ }
73
+ logger.log(chalk.yellow(`\nThis will permanently remove all ${count} secret key(s) from ${whereLabel}.`));
74
+ logger.log(chalk.gray('This cannot be undone.\n'));
75
+ const answer = (await promptLine(chalk.bold('Type "yes" to confirm (anything else cancels): '))).toLowerCase();
76
+ return answer === 'yes';
77
+ }
78
+
79
+ /**
80
+ * @param {Object} auth - Remote dev auth (serverUrl, clientCertPem, serverCaPem)
81
+ * @param {string} target - Secrets endpoint URL
82
+ * @returns {Promise<string[]>}
83
+ */
84
+ async function listRemoteSecretKeys(auth, target) {
85
+ const items = await devApi.listSecrets(
86
+ auth.serverUrl,
87
+ auth.clientCertPem,
88
+ auth.serverCaPem || undefined,
89
+ target
90
+ );
91
+ return (Array.isArray(items) ? items : [])
92
+ .map(i => i.name || i.key || '')
93
+ .filter(k => typeof k === 'string' && k.length > 0)
94
+ .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
95
+ }
96
+
97
+ /**
98
+ * @param {string} target - Secrets endpoint URL
99
+ * @returns {string}
100
+ */
101
+ function labelForRemoteSharedSecrets(target) {
102
+ const host = remoteDevAuth.getSharedSecretsRemoteHostname(target);
103
+ return host ? `shared secrets (remote - ${host})` : 'shared secrets (remote)';
104
+ }
105
+
106
+ /**
107
+ * @param {Object} auth
108
+ * @param {string[]} keys
109
+ * @param {string} target
110
+ * @returns {Promise<void>}
111
+ */
112
+ async function deleteRemoteSecretKeys(auth, keys, target) {
113
+ const ca = auth.serverCaPem || undefined;
114
+ for (const key of keys) {
115
+ await devApi.deleteSecret(auth.serverUrl, auth.clientCertPem, key, ca, target);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Remove every shared secret via remote API.
121
+ * @param {string} target - Resolved secrets endpoint URL
122
+ * @param {Object} options - { yes?: boolean }
123
+ * @returns {Promise<void>}
124
+ */
125
+ async function removeAllRemoteSharedSecrets(target, options) {
126
+ const auth = await remoteDevAuth.getRemoteDevAuth();
127
+ if (!auth) {
128
+ throw new Error('Remote server not configured or certificate missing. Run "aifabrix dev init" first.');
129
+ }
130
+ const keys = await listRemoteSecretKeys(auth, target);
131
+ if (keys.length === 0) {
132
+ logger.log(chalk.gray('No shared secrets to remove.'));
133
+ return;
134
+ }
135
+ const where = labelForRemoteSharedSecrets(target);
136
+ const ok = await confirmRemoveAll(!!options.yes, where, keys.length);
137
+ if (!ok) {
138
+ logger.log(chalk.gray('Cancelled. No secrets were removed.'));
139
+ return;
140
+ }
141
+ await deleteRemoteSecretKeys(auth, keys, target);
142
+ logger.log(formatSuccessLine(`Removed ${keys.length} secret key(s) from ${where}.`));
143
+ }
144
+
145
+ /**
146
+ * Remove every key from a shared secrets YAML file.
147
+ * @param {string} resolvedPath - Absolute file path
148
+ * @param {Object} options - { yes?: boolean }
149
+ * @returns {Promise<void>}
150
+ */
151
+ async function removeAllSharedFileSecrets(resolvedPath, options) {
152
+ const keys = listKeysFromYamlFile(resolvedPath);
153
+ if (keys.length === 0) {
154
+ logger.log(chalk.gray('No shared secrets to remove.'));
155
+ return;
156
+ }
157
+ const ok = await confirmRemoveAll(!!options.yes, `shared secrets file (${resolvedPath})`, keys.length);
158
+ if (!ok) {
159
+ logger.log(chalk.gray('Cancelled. No secrets were removed.'));
160
+ return;
161
+ }
162
+ writeEmptySecretsFile(resolvedPath);
163
+ logger.log(formatSuccessLine(`Removed ${keys.length} secret key(s) from shared secrets file.`));
164
+ }
165
+
166
+ /**
167
+ * Remove every key from shared store (remote API or file).
168
+ * @param {string} generalSecretsPath - Path or URL from config
169
+ * @param {Object} options - { yes?: boolean }
170
+ * @returns {Promise<void>}
171
+ */
172
+ async function removeAllSharedSecrets(generalSecretsPath, options) {
173
+ const target = await remoteDevAuth.resolveSharedSecretsEndpoint(generalSecretsPath);
174
+ if (remoteDevAuth.isRemoteSecretsUrl(target)) {
175
+ await removeAllRemoteSharedSecrets(target, options);
176
+ return;
177
+ }
178
+ const resolvedPath = path.isAbsolute(target) ? target : path.resolve(process.cwd(), target);
179
+ await removeAllSharedFileSecrets(resolvedPath, options);
180
+ }
181
+
182
+ /**
183
+ * @param {Object} options - { yes?: boolean }
184
+ * @returns {Promise<void>}
185
+ */
186
+ async function removeAllUserSecrets(options) {
187
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
188
+ const keys = listKeysFromYamlFile(userSecretsPath);
189
+ if (keys.length === 0) {
190
+ logger.log(chalk.gray('No user secrets to remove.'));
191
+ return;
192
+ }
193
+ const ok = await confirmRemoveAll(!!options.yes, `user secrets (${userSecretsPath})`, keys.length);
194
+ if (!ok) {
195
+ logger.log(chalk.gray('Cancelled. No secrets were removed.'));
196
+ return;
197
+ }
198
+ writeEmptySecretsFile(userSecretsPath);
199
+ logger.log(formatSuccessLine(`Removed ${keys.length} secret key(s) from user secrets.`));
200
+ }
201
+
202
+ /**
203
+ * Handle secret remove-all command.
204
+ * @param {Object} options - { shared?: boolean, yes?: boolean }
205
+ * @returns {Promise<void>}
206
+ */
207
+ async function handleSecretsRemoveAll(options) {
208
+ const isShared = options.shared || options['shared'] || false;
209
+ if (!isShared) {
210
+ await removeAllUserSecrets(options);
211
+ return;
212
+ }
213
+ const generalSecretsPath = await getAifabrixSecretsPath();
214
+ if (!generalSecretsPath) {
215
+ throw new Error('Shared secrets not configured. Set aifabrix-secrets in config.yaml.');
216
+ }
217
+ await removeAllSharedSecrets(generalSecretsPath, options);
218
+ }
219
+
220
+ module.exports = { handleSecretsRemoveAll };
@@ -4,14 +4,14 @@
4
4
  * @version 2.0.0
5
5
  */
6
6
 
7
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
7
8
  const path = require('path');
8
9
  const fs = require('fs');
9
10
  const yaml = require('js-yaml');
10
- const chalk = require('chalk');
11
11
  const logger = require('../utils/logger');
12
12
  const { getAifabrixSecretsPath } = require('../core/config');
13
13
  const pathsUtil = require('../utils/paths');
14
- const { isRemoteSecretsUrl, getRemoteDevAuth } = require('../utils/remote-dev-auth');
14
+ const remoteDevAuth = require('../utils/remote-dev-auth');
15
15
  const devApi = require('../api/dev.api');
16
16
 
17
17
  /**
@@ -44,27 +44,36 @@ function removeKeyFromFile(key, filePath) {
44
44
  * @returns {Promise<void>}
45
45
  */
46
46
  async function removeSharedSecret(key, generalSecretsPath) {
47
- if (isRemoteSecretsUrl(generalSecretsPath)) {
48
- const auth = await getRemoteDevAuth();
47
+ const target = await remoteDevAuth.resolveSharedSecretsEndpoint(generalSecretsPath);
48
+ if (remoteDevAuth.isRemoteSecretsUrl(target)) {
49
+ const auth = await remoteDevAuth.getRemoteDevAuth();
49
50
  if (!auth) {
50
51
  throw new Error('Remote server not configured or certificate missing. Run "aifabrix dev init" first.');
51
52
  }
52
53
  try {
53
- await devApi.deleteSecret(auth.serverUrl, auth.clientCertPem, key);
54
+ await devApi.deleteSecret(
55
+ auth.serverUrl,
56
+ auth.clientCertPem,
57
+ key,
58
+ auth.serverCaPem || undefined,
59
+ target
60
+ );
54
61
  } catch (err) {
55
62
  if (err.status === 404) {
56
63
  throw new Error(`Secret '${key}' not found.`);
57
64
  }
58
65
  throw err;
59
66
  }
60
- logger.log(chalk.green(`✓ Secret '${key}' removed from remote shared secrets.`));
67
+ const host = remoteDevAuth.getSharedSecretsRemoteHostname(target);
68
+ const where = host ? `shared secrets (remote - ${host})` : 'shared secrets (remote)';
69
+ logger.log(formatSuccessLine(`Secret '${key}' removed from ${where}.`));
61
70
  return;
62
71
  }
63
- const resolvedPath = path.isAbsolute(generalSecretsPath)
64
- ? generalSecretsPath
65
- : path.resolve(process.cwd(), generalSecretsPath);
72
+ const resolvedPath = path.isAbsolute(target)
73
+ ? target
74
+ : path.resolve(process.cwd(), target);
66
75
  removeKeyFromFile(key, resolvedPath);
67
- logger.log(chalk.green(`✓ Secret '${key}' removed from shared secrets file.`));
76
+ logger.log(formatSuccessLine(`Secret '${key}' removed from shared secrets file.`));
68
77
  }
69
78
 
70
79
  /**
@@ -88,9 +97,9 @@ async function handleSecretsRemove(key, options) {
88
97
  }
89
98
  await removeSharedSecret(key, generalSecretsPath);
90
99
  } else {
91
- const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
100
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
92
101
  removeKeyFromFile(key, userSecretsPath);
93
- logger.log(chalk.green(`✓ Secret '${key}' removed from user secrets.`));
102
+ logger.log(formatSuccessLine(`Secret '${key}' removed from user secrets.`));
94
103
  }
95
104
  }
96
105
 
@@ -9,13 +9,13 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
12
13
  const path = require('path');
13
- const chalk = require('chalk');
14
14
  const logger = require('../utils/logger');
15
15
  const { getAifabrixSecretsPath } = require('../core/config');
16
16
  const { saveLocalSecret, saveSecret } = require('../utils/local-secrets');
17
17
  const pathsUtil = require('../utils/paths');
18
- const { isRemoteSecretsUrl, getRemoteDevAuth } = require('../utils/remote-dev-auth');
18
+ const remoteDevAuth = require('../utils/remote-dev-auth');
19
19
  const devApi = require('../api/dev.api');
20
20
 
21
21
  /**
@@ -39,20 +39,29 @@ const devApi = require('../api/dev.api');
39
39
  * @returns {Promise<void>}
40
40
  */
41
41
  async function setSharedSecret(key, value, generalSecretsPath) {
42
- if (isRemoteSecretsUrl(generalSecretsPath)) {
43
- const auth = await getRemoteDevAuth();
42
+ const target = await remoteDevAuth.resolveSharedSecretsEndpoint(generalSecretsPath);
43
+ if (remoteDevAuth.isRemoteSecretsUrl(target)) {
44
+ const auth = await remoteDevAuth.getRemoteDevAuth();
44
45
  if (!auth) {
45
46
  throw new Error('Remote server not configured or certificate missing. Run "aifabrix dev init" first.');
46
47
  }
47
- await devApi.addSecret(auth.serverUrl, auth.clientCertPem, { key, value });
48
- logger.log(chalk.green(`✓ Secret '${key}' saved to remote secrets (shared).`));
48
+ await devApi.addSecret(
49
+ auth.serverUrl,
50
+ auth.clientCertPem,
51
+ { key, value },
52
+ auth.serverCaPem || undefined,
53
+ target
54
+ );
55
+ const host = remoteDevAuth.getSharedSecretsRemoteHostname(target);
56
+ const where = host ? `shared secrets (remote - ${host})` : 'shared secrets (remote)';
57
+ logger.log(formatSuccessLine(`Secret '${key}' saved to ${where}.`));
49
58
  return;
50
59
  }
51
- const resolvedPath = path.isAbsolute(generalSecretsPath)
52
- ? generalSecretsPath
53
- : path.resolve(process.cwd(), generalSecretsPath);
60
+ const resolvedPath = path.isAbsolute(target)
61
+ ? target
62
+ : path.resolve(process.cwd(), target);
54
63
  await saveSecret(key, value, resolvedPath);
55
- logger.log(chalk.green(`✓ Secret '${key}' saved to general secrets file: ${resolvedPath}`));
64
+ logger.log(formatSuccessLine(`Secret '${key}' saved to general secrets file: ${resolvedPath}`));
56
65
  }
57
66
 
58
67
  async function handleSecretsSet(key, value, options) {
@@ -80,8 +89,8 @@ async function handleSecretsSet(key, value, options) {
80
89
  await setSharedSecret(key, value, generalSecretsPath);
81
90
  } else {
82
91
  await saveLocalSecret(key, value);
83
- const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
84
- logger.log(chalk.green(`✓ Secret '${key}' saved to user secrets file: ${userSecretsPath}`));
92
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
93
+ logger.log(formatSuccessLine(`Secret '${key}' saved to user secrets file: ${userSecretsPath}`));
85
94
  }
86
95
  }
87
96
 
@@ -1,3 +1,4 @@
1
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder – Secrets validate command
3
4
  *
@@ -9,7 +10,6 @@
9
10
  */
10
11
 
11
12
  const chalk = require('chalk');
12
- const path = require('path');
13
13
  const logger = require('../utils/logger');
14
14
  const { validateSecretsFile } = require('../utils/secrets-validation');
15
15
  const { validateDataplaneSecrets } = require('../utils/token-manager');
@@ -34,7 +34,7 @@ async function handleSecretsValidate(pathArg, options = {}) {
34
34
  if (target.type === 'file' && target.filePath) {
35
35
  filePath = target.filePath;
36
36
  } else {
37
- filePath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
37
+ filePath = pathsUtil.getPrimaryUserSecretsLocalPath();
38
38
  }
39
39
  }
40
40
 
@@ -42,9 +42,9 @@ async function handleSecretsValidate(pathArg, options = {}) {
42
42
  const dataplaneResult = validateDataplaneSecrets(filePath);
43
43
  const allValid = result.valid && dataplaneResult.valid;
44
44
  if (result.valid) {
45
- logger.log(chalk.green(`✓ Secrets file is valid: ${result.path}`));
45
+ logger.log(formatSuccessLine(`Secrets file is valid: ${result.path}`));
46
46
  } else {
47
- logger.log(chalk.red(`✗ Validation failed: ${result.path}`));
47
+ logger.log(formatBlockingError(`Validation failed: ${result.path}`));
48
48
  result.errors.forEach((err) => logger.log(chalk.yellow(` • ${err}`)));
49
49
  }
50
50
  if (!dataplaneResult.valid) {
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder - Secure Command
3
4
  *
@@ -32,7 +33,7 @@ async function findSecretsFiles() {
32
33
  const files = [];
33
34
 
34
35
  // User's secrets file
35
- const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
36
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
36
37
  if (fs.existsSync(userSecretsPath)) {
37
38
  files.push({ path: userSecretsPath, type: 'user' });
38
39
  }
@@ -131,7 +132,7 @@ async function getEncryptionKey(options) {
131
132
  // Check if key already exists in config
132
133
  const existingKey = await getSecretsEncryptionKey();
133
134
  if (existingKey) {
134
- logger.log(chalk.yellow('⚠️ Encryption key already configured in config.yaml'));
135
+ logger.log(chalk.yellow(' Encryption key already configured in config.yaml'));
135
136
  const useExisting = await inquirer.prompt([{
136
137
  type: 'confirm',
137
138
  name: 'use',
@@ -143,18 +144,18 @@ async function getEncryptionKey(options) {
143
144
  } else {
144
145
  encryptionKey = await promptForEncryptionKey();
145
146
  await setSecretsEncryptionKey(encryptionKey);
146
- logger.log(chalk.green('Encryption key saved to config.yaml'));
147
+ logger.log(formatSuccessLine('Encryption key saved to config.yaml'));
147
148
  }
148
149
  } else {
149
150
  encryptionKey = await promptForEncryptionKey();
150
151
  await setSecretsEncryptionKey(encryptionKey);
151
- logger.log(chalk.green('Encryption key saved to config.yaml'));
152
+ logger.log(formatSuccessLine('Encryption key saved to config.yaml'));
152
153
  }
153
154
  } else {
154
155
  // Validate and save the provided key
155
156
  validateEncryptionKey(encryptionKey);
156
157
  await setSecretsEncryptionKey(encryptionKey);
157
- logger.log(chalk.green('Encryption key saved to config.yaml'));
158
+ logger.log(formatSuccessLine('Encryption key saved to config.yaml'));
158
159
  }
159
160
  return encryptionKey;
160
161
  }
@@ -178,12 +179,12 @@ async function processSecretsFiles(secretsFiles, encryptionKey) {
178
179
  totalValues += result.total;
179
180
 
180
181
  if (result.encrypted > 0) {
181
- logger.log(chalk.green(` Encrypted ${result.encrypted} of ${result.total} values`));
182
+ logger.log(chalk.green(` Encrypted ${result.encrypted} of ${result.total} values`));
182
183
  } else {
183
184
  logger.log(chalk.gray(` - All values already encrypted (${result.total} total)`));
184
185
  }
185
186
  } catch (error) {
186
- logger.log(chalk.red(` Error: ${error.message}`));
187
+ logger.log(chalk.red(` Error: ${error.message}`));
187
188
  }
188
189
  }
189
190
 
@@ -197,7 +198,7 @@ async function processSecretsFiles(secretsFiles, encryptionKey) {
197
198
  * @param {number} totalValues - Total number of values
198
199
  */
199
200
  function displayEncryptionSummary(filesCount, totalEncrypted, totalValues) {
200
- logger.log(chalk.green('\n✅ Encryption complete!'));
201
+ logger.log(formatSuccessParagraph('Encryption complete!'));
201
202
  logger.log(chalk.gray(` Files processed: ${filesCount}`));
202
203
  logger.log(chalk.gray(` Values encrypted: ${totalEncrypted} of ${totalValues} total`));
203
204
  logger.log(chalk.gray(' Encryption key stored in: ~/.aifabrix/config.yaml\n'));
@@ -224,7 +225,7 @@ async function handleSecure(options) {
224
225
  const secretsFiles = await findSecretsFiles();
225
226
 
226
227
  if (secretsFiles.length === 0) {
227
- logger.log(chalk.yellow('⚠️ No secrets files found to encrypt'));
228
+ logger.log(chalk.yellow(' No secrets files found to encrypt'));
228
229
  logger.log(chalk.gray(' Create ~/.aifabrix/secrets.local.yaml or configure aifabrix-secrets in config.yaml'));
229
230
  return;
230
231
  }
@@ -1,3 +1,4 @@
1
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Service user create command – create service user and get one-time secret
3
4
  * POST /api/v1/service-users. Used by `aifabrix service-user create`.
@@ -75,14 +76,14 @@ function handleCreateError(response) {
75
76
  const status = response.status;
76
77
  const msg = response.formattedError || response.error || 'Request failed';
77
78
  if (status === 400) {
78
- logger.error(chalk.red(`❌ Validation error: ${msg}`));
79
+ logger.error(formatBlockingError(`Validation error: ${msg}`));
79
80
  } else if (status === 401) {
80
- logger.error(chalk.red('Unauthorized. Run "aifabrix login" and try again.'));
81
+ logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
81
82
  } else if (status === 403) {
82
- logger.error(chalk.red('Missing permission: service-user:create'));
83
+ logger.error(formatBlockingError('Missing permission: service-user:create'));
83
84
  logger.error(chalk.gray('Your account needs the service-user:create permission on the controller.'));
84
85
  } else {
85
- logger.error(chalk.red(`❌ Failed to create service user: ${msg}`));
86
+ logger.error(formatBlockingError(`Failed to create service user: ${msg}`));
86
87
  }
87
88
  process.exit(1);
88
89
  }
@@ -96,20 +97,20 @@ function handleServiceUserApiError(response, permissionScope) {
96
97
  const status = response.status;
97
98
  const msg = response.formattedError || response.error || 'Request failed';
98
99
  if (status === 400) {
99
- logger.error(chalk.red(`❌ Validation error: ${msg}`));
100
+ logger.error(formatBlockingError(`Validation error: ${msg}`));
100
101
  } else if (status === 401) {
101
- logger.error(chalk.red('Unauthorized. Run "aifabrix login" and try again.'));
102
+ logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
102
103
  } else if (status === 403) {
103
- logger.error(chalk.red(`❌ Missing permission: service-user:${permissionScope}`));
104
+ logger.error(formatBlockingError(`Missing permission: service-user:${permissionScope}`));
104
105
  logger.error(chalk.gray(`Your account needs the service-user:${permissionScope} permission on the controller.`));
105
106
  } else if (status === 404) {
106
- logger.error(chalk.red('Service user not found.'));
107
+ logger.error(formatBlockingError('Service user not found.'));
107
108
  const detail = response.error || '';
108
109
  if (detail) {
109
110
  logger.error(chalk.gray(detail));
110
111
  }
111
112
  } else {
112
- logger.error(chalk.red(`❌ Request failed: ${msg}`));
113
+ logger.error(formatBlockingError(`Request failed: ${msg}`));
113
114
  }
114
115
  process.exit(1);
115
116
  }
@@ -123,12 +124,12 @@ function handleServiceUserApiError(response, permissionScope) {
123
124
  async function resolveControllerAndAuth(options) {
124
125
  const controllerUrl = options.controller || (await resolveControllerUrl());
125
126
  if (!controllerUrl) {
126
- logger.error(chalk.red('Controller URL is required. Run "aifabrix login" first.'));
127
+ logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
127
128
  process.exit(1);
128
129
  }
129
130
  const authResult = await getServiceUserAuth(controllerUrl);
130
131
  if (!authResult || !authResult.token) {
131
- logger.error(chalk.red(`❌ No authentication token for controller: ${controllerUrl}`));
132
+ logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
132
133
  logger.error(chalk.gray('Run: aifabrix login'));
133
134
  process.exit(1);
134
135
  }
@@ -173,7 +174,7 @@ function displayServiceUserList(items) {
173
174
  * @param {string} clientSecret - One-time client secret
174
175
  */
175
176
  function displayCreateSuccess(clientId, clientSecret) {
176
- logger.log(chalk.bold('\n Service user created\n'));
177
+ logger.log(chalk.bold('\n Service user created\n'));
177
178
  logger.log(chalk.cyan(' clientId: ') + clientId);
178
179
  logger.log(chalk.cyan(' clientSecret: ') + clientSecret);
179
180
  logger.log('');
@@ -207,19 +208,19 @@ function validateServiceUserOptions(options) {
207
208
  const redirectUris = parseList(options.redirectUris);
208
209
  const groupNames = parseList(options.groupNames);
209
210
  if (!username) {
210
- logger.error(chalk.red('Username is required. Use --username <username>.'));
211
+ logger.error(formatBlockingError('Username is required. Use --username <username>.'));
211
212
  process.exit(1);
212
213
  }
213
214
  if (!email) {
214
- logger.error(chalk.red('Email is required. Use --email <email>.'));
215
+ logger.error(formatBlockingError('Email is required. Use --email <email>.'));
215
216
  process.exit(1);
216
217
  }
217
218
  if (redirectUris.length === 0) {
218
- logger.error(chalk.red('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
219
+ logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
219
220
  process.exit(1);
220
221
  }
221
222
  if (groupNames.length === 0) {
222
- logger.error(chalk.red('At least one group name is required. Use --group-names <name1,name2,...>.'));
223
+ logger.error(formatBlockingError('At least one group name is required. Use --group-names <name1,name2,...>.'));
223
224
  process.exit(1);
224
225
  }
225
226
  return {
@@ -241,12 +242,12 @@ async function resolveOptionsAndAuth(options) {
241
242
  const validated = validateServiceUserOptions(options);
242
243
  const controllerUrl = options.controller || (await resolveControllerUrl());
243
244
  if (!controllerUrl) {
244
- logger.error(chalk.red('Controller URL is required. Run "aifabrix login" first.'));
245
+ logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
245
246
  process.exit(1);
246
247
  }
247
248
  const authResult = await getServiceUserAuth(controllerUrl);
248
249
  if (!authResult || !authResult.token) {
249
- logger.error(chalk.red(`❌ No authentication token for controller: ${controllerUrl}`));
250
+ logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
250
251
  logger.error(chalk.gray('Run: aifabrix login'));
251
252
  process.exit(1);
252
253
  }
@@ -295,7 +296,7 @@ async function runServiceUserCreate(options = {}) {
295
296
  function requireServiceUserId(id) {
296
297
  const trimmed = (id && typeof id === 'string' ? id.trim() : '') || '';
297
298
  if (!trimmed) {
298
- logger.error(chalk.red('Service user ID is required. Use --id <uuid>.'));
299
+ logger.error(formatBlockingError('Service user ID is required. Use --id <uuid>.'));
299
300
  process.exit(1);
300
301
  }
301
302
  return trimmed;
@@ -343,7 +344,7 @@ async function runServiceUserRotateSecret(options = {}) {
343
344
  const payload = response?.data?.data ?? response?.data ?? response ?? {};
344
345
  const clientSecret = payload?.clientSecret ?? '';
345
346
  if (response && response.success === true) {
346
- logger.log(chalk.bold('\n Secret rotated\n'));
347
+ logger.log(chalk.bold('\n Secret rotated\n'));
347
348
  logger.log(chalk.cyan(' clientSecret: ') + clientSecret);
348
349
  logger.log('');
349
350
  logger.log(chalk.yellow('⚠ ' + ONE_TIME_WARNING));
@@ -366,7 +367,7 @@ async function runServiceUserDelete(options = {}) {
366
367
  return;
367
368
  }
368
369
  if (response && response.success === true) {
369
- logger.log(chalk.green('Service user deactivated.\n'));
370
+ logger.log(formatSuccessLine('Service user deactivated.\n'));
370
371
  }
371
372
  }
372
373
 
@@ -380,7 +381,7 @@ async function runServiceUserUpdateGroups(options = {}) {
380
381
  const id = requireServiceUserId(options.id);
381
382
  const groupNames = parseList(options.groupNames);
382
383
  if (groupNames.length === 0) {
383
- logger.error(chalk.red('At least one group name is required. Use --group-names <name1,name2,...>.'));
384
+ logger.error(formatBlockingError('At least one group name is required. Use --group-names <name1,name2,...>.'));
384
385
  process.exit(1);
385
386
  }
386
387
  const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
@@ -390,7 +391,7 @@ async function runServiceUserUpdateGroups(options = {}) {
390
391
  return;
391
392
  }
392
393
  if (response && response.success === true) {
393
- logger.log(chalk.green('Service user groups updated.\n'));
394
+ logger.log(formatSuccessLine('Service user groups updated.\n'));
394
395
  }
395
396
  }
396
397
 
@@ -404,7 +405,7 @@ async function runServiceUserUpdateRedirectUris(options = {}) {
404
405
  const id = requireServiceUserId(options.id);
405
406
  const redirectUris = parseList(options.redirectUris);
406
407
  if (redirectUris.length === 0) {
407
- logger.error(chalk.red('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
408
+ logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
408
409
  process.exit(1);
409
410
  }
410
411
  const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
@@ -414,7 +415,7 @@ async function runServiceUserUpdateRedirectUris(options = {}) {
414
415
  return;
415
416
  }
416
417
  if (response && response.success === true) {
417
- logger.log(chalk.green('Service user redirect URIs updated.\n'));
418
+ logger.log(formatSuccessLine('Service user redirect URIs updated.\n'));
418
419
  }
419
420
  }
420
421