@aifabrix/builder 2.49.1 → 2.50.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 (413) hide show
  1. package/.cursor/process/README.md +9 -0
  2. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +49 -0
  3. package/.cursor/rules/plan-rules/application-lifecycle.mdc +30 -0
  4. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +35 -0
  5. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +31 -0
  6. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +34 -0
  7. package/.cursor/skills/README.md +1 -0
  8. package/.dockerignore +52 -0
  9. package/.eslintignore +22 -0
  10. package/README.md +2 -0
  11. package/bin/builder-api-cleanup.js +26 -0
  12. package/bin/builder-api-nest.js +10 -0
  13. package/bin/builder-api-spike.js +19 -0
  14. package/enterprise-knowledge/README.md +50 -0
  15. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +22 -0
  16. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +15 -0
  17. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +69 -0
  18. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +52 -0
  19. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +29 -0
  20. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +2 -0
  21. package/integration/README.md +81 -0
  22. package/integration/github/README.md +53 -0
  23. package/integration/github/application.json +21 -0
  24. package/integration/github/deploy.js +72 -0
  25. package/integration/github/env.template +11 -0
  26. package/integration/github/github-datasource-issue-comments.json +239 -0
  27. package/integration/github/github-datasource-issues.json +399 -0
  28. package/integration/github/github-datasource-repository.json +354 -0
  29. package/integration/github/github-deploy.json +1202 -0
  30. package/integration/github/github-system.json +82 -0
  31. package/integration/hubspot-test/README.md +1 -1
  32. package/jest.isolated-projects.js +31 -1
  33. package/jest.projects.js +87 -1
  34. package/lib/api/dev-server-https.js +1 -1
  35. package/lib/api/dev.api.js +1 -0
  36. package/lib/api/enterprise-knowledge.api.js +63 -0
  37. package/lib/api/evidence.api.js +172 -0
  38. package/lib/api/external-systems.api.js +8 -2
  39. package/lib/api/host-ops.api.js +69 -0
  40. package/lib/api/resource-types.api.js +1 -0
  41. package/lib/api/types/enterprise-knowledge.types.js +46 -0
  42. package/lib/api/types/evidence.types.js +67 -0
  43. package/lib/api/types/resource-types.types.js +2 -0
  44. package/lib/app/controller-application-filter.js +36 -0
  45. package/lib/app/deploy.js +1 -1
  46. package/lib/app/list-external-systems.js +130 -0
  47. package/lib/app/list.js +20 -8
  48. package/lib/app/run.js +3 -1
  49. package/lib/app/show-display.js +8 -3
  50. package/lib/app/show-online.js +178 -0
  51. package/lib/app/show.js +37 -88
  52. package/lib/build/builder-api-docker-staging.js +47 -0
  53. package/lib/build/miso-controller-npm-sdk-guard.js +2 -2
  54. package/lib/build/standard-docker-build.js +2 -0
  55. package/lib/certification/post-unified-cert-sync.js +4 -31
  56. package/lib/certification/sync-after-external-command.js +0 -1
  57. package/lib/certification/sync-system-certification.js +0 -3
  58. package/lib/channels/add-channel.js +40 -34
  59. package/lib/channels/approval-guides/chatgpt.js +33 -0
  60. package/lib/channels/approval-guides/credential-portal.js +38 -0
  61. package/lib/channels/approval-guides/index.js +110 -0
  62. package/lib/channels/approval-guides/microsoft-copilot.js +34 -0
  63. package/lib/channels/approval-guides/slack.js +34 -0
  64. package/lib/channels/approval-guides/teams-bot.js +35 -0
  65. package/lib/channels/approval-guides/web-embed.js +32 -0
  66. package/lib/channels/channel-approval-guide.js +11 -0
  67. package/lib/channels/channel-artifact-generator.js +16 -4
  68. package/lib/channels/channel-audit.js +3 -0
  69. package/lib/channels/channel-auth-context.js +1 -1
  70. package/lib/channels/channel-description.js +59 -0
  71. package/lib/channels/channel-integration-generator.js +13 -230
  72. package/lib/channels/channel-integration-template.js +94 -0
  73. package/lib/channels/channel-runtime-resolver.js +43 -3
  74. package/lib/channels/channel-status-display.js +66 -26
  75. package/lib/channels/channel-targets.js +3 -3
  76. package/lib/cli/index.js +27 -3
  77. package/lib/cli/infra-guided.js +5 -1
  78. package/lib/cli/setup-app-external-create.js +111 -0
  79. package/lib/cli/setup-app.help.js +1 -1
  80. package/lib/cli/setup-app.js +38 -125
  81. package/lib/cli/setup-app.test-commands.js +26 -52
  82. package/lib/cli/setup-certification-commands.js +4 -4
  83. package/lib/cli/setup-certification.help.js +9 -9
  84. package/lib/cli/setup-dev-path-commands.js +24 -0
  85. package/lib/cli/setup-dev.js +115 -16
  86. package/lib/cli/setup-external-system-help.js +4 -6
  87. package/lib/cli/setup-external-system.js +58 -52
  88. package/lib/cli/setup-infra-up-builder-api-action.js +64 -0
  89. package/lib/cli/setup-infra-up-platform-action.js +3 -1
  90. package/lib/cli/setup-infra.js +15 -0
  91. package/lib/cli/setup-list.js +43 -0
  92. package/lib/cli/setup-utility-repair.js +26 -7
  93. package/lib/cli/setup-utility.js +5 -3
  94. package/lib/commands/app.js +15 -43
  95. package/lib/commands/channel-add-setup.js +110 -0
  96. package/lib/commands/channel.js +42 -8
  97. package/lib/commands/datasource-sync-display.js +96 -0
  98. package/lib/commands/datasource-sync-policy.js +210 -0
  99. package/lib/commands/datasource-test-trust-cli.js +6 -6
  100. package/lib/commands/datasource-unified-test-cli.js +2 -1
  101. package/lib/commands/datasource-unified-test-cli.options.js +33 -23
  102. package/lib/commands/deploy-gate-failure-details.js +163 -0
  103. package/lib/commands/deploy-gate-orchestrator.js +322 -0
  104. package/lib/commands/dev-cli-handlers.js +64 -61
  105. package/lib/commands/dev-host-ops-params.js +91 -0
  106. package/lib/commands/dev-host-ops-poll.js +111 -0
  107. package/lib/commands/dev-host-ops.js +252 -0
  108. package/lib/commands/dev-init.js +3 -1
  109. package/lib/commands/enterprise-knowledge.js +284 -0
  110. package/lib/commands/evidence-cli-leaves.js +250 -0
  111. package/lib/commands/evidence.js +51 -0
  112. package/lib/commands/governance-cli-shared.js +2 -2
  113. package/lib/commands/governance-command-action.js +2 -2
  114. package/lib/commands/governance-fixture-subjects.js +85 -0
  115. package/lib/commands/governance.js +1 -1
  116. package/lib/commands/lifecycle-command-action.js +2 -2
  117. package/lib/commands/lifecycle-external.js +3 -3
  118. package/lib/commands/manifest-cli-auth.js +29 -0
  119. package/lib/commands/manifest-cli-commands.js +170 -0
  120. package/lib/commands/manifest-cli-integration-path.js +63 -0
  121. package/lib/commands/manifest.js +21 -0
  122. package/lib/commands/protection-cli-leaves.js +3 -3
  123. package/lib/commands/repair-auth-apply.js +15 -1
  124. package/lib/commands/repair-authentications-454.js +169 -0
  125. package/lib/commands/repair-capability-openapi-warnings.js +126 -0
  126. package/lib/commands/repair-datasource-apply.js +135 -0
  127. package/lib/commands/repair-datasource-auto-rbac.js +62 -1
  128. package/lib/commands/repair-datasource-http-service-contract.js +239 -0
  129. package/lib/commands/repair-datasource-operation-security.js +180 -0
  130. package/lib/commands/repair-datasource-run.js +7 -2
  131. package/lib/commands/repair-datasource-sync-operations.js +177 -0
  132. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +133 -0
  133. package/lib/commands/repair-datasource.js +12 -68
  134. package/lib/commands/repair-env-template.js +11 -1
  135. package/lib/commands/repair-external-run.js +121 -31
  136. package/lib/commands/repair-internal.js +1 -1
  137. package/lib/commands/repair-openapi-export.js +156 -0
  138. package/lib/commands/repair-openapi-sync.js +19 -2
  139. package/lib/commands/repair-orchestration-datasource-split.js +259 -0
  140. package/lib/commands/repair-persist.js +22 -1
  141. package/lib/commands/repair-rbac-platform-preset.js +204 -0
  142. package/lib/commands/repair-rbac-reader-roles.js +191 -0
  143. package/lib/commands/repair-rbac.js +163 -86
  144. package/lib/commands/repair-steps.js +83 -18
  145. package/lib/commands/repair-system-permissions.js +1 -0
  146. package/lib/commands/repair.js +1 -1
  147. package/lib/commands/resource-type.js +65 -26
  148. package/lib/commands/run-post-publish-data-sync.js +68 -0
  149. package/lib/commands/setup-modes.js +1 -1
  150. package/lib/commands/test-e2e-external.js +21 -8
  151. package/lib/commands/test-governance-cli-options.js +2 -2
  152. package/lib/commands/test-trust-cli-options.js +2 -2
  153. package/lib/commands/up-builder-api-bootstrap.js +71 -0
  154. package/lib/commands/up-builder-api-provision.js +241 -0
  155. package/lib/commands/up-builder-api.js +88 -0
  156. package/lib/commands/up-common.js +25 -1
  157. package/lib/commands/upload-credential-push-log.js +117 -0
  158. package/lib/commands/upload-data-sync-finalize.js +60 -0
  159. package/lib/commands/upload-probe.js +66 -0
  160. package/lib/commands/upload-publish-context.js +4 -1
  161. package/lib/commands/upload-sidecar-followups.js +55 -27
  162. package/lib/commands/upload.js +116 -157
  163. package/lib/commands/verify-governance-combined-external.js +1 -1
  164. package/lib/commands/verify-governance-db.js +2 -2
  165. package/lib/commands/verify-operations-command-action.js +2 -2
  166. package/lib/commands/verify-operations-step-plan.js +2 -2
  167. package/lib/commands/verify-operations-steps.js +2 -2
  168. package/lib/commands/verify-trust-command-action.js +1 -1
  169. package/lib/commands/wizard-core.js +1 -1
  170. package/lib/commands/wizard-headless.js +2 -1
  171. package/lib/core/admin-secrets.js +2 -6
  172. package/lib/core/secrets-env-content.js +10 -4
  173. package/lib/core/secrets-env-write.js +2 -13
  174. package/lib/datasource/agent-trust-run.js +3 -3
  175. package/lib/datasource/deploy.js +3 -1
  176. package/lib/datasource/test-e2e.js +6 -1
  177. package/lib/datasource/unified-validation-run-body.js +4 -1
  178. package/lib/datasource/unified-validation-run.js +1 -1
  179. package/lib/deployment/deploy-poll-ui.js +73 -12
  180. package/lib/deployment/deployer-poll-run.js +113 -0
  181. package/lib/deployment/deployer.js +17 -87
  182. package/lib/evidence/dataplane-context.js +21 -0
  183. package/lib/evidence/evidence-display.js +78 -0
  184. package/lib/evidence/evidence-service.js +392 -0
  185. package/lib/external-system/deploy.js +290 -112
  186. package/lib/external-system/download.js +7 -3
  187. package/lib/external-system/test-execution.js +1 -1
  188. package/lib/external-system/test-system-level.js +8 -10
  189. package/lib/external-system/test.js +12 -6
  190. package/lib/generator/deploy-script.js +73 -0
  191. package/lib/generator/external.js +2 -1
  192. package/lib/generator/index.js +17 -2
  193. package/lib/generator/wizard.js +3 -36
  194. package/lib/internal/fs-real-sync.js +22 -1
  195. package/lib/internal/node-fs.js +2 -0
  196. package/lib/lifecycle/lifecycle-governance-options.js +1 -1
  197. package/lib/lifecycle/verify-step-progress.js +1 -1
  198. package/lib/programmatic/access-log.js +26 -0
  199. package/lib/programmatic/auth-config.js +44 -0
  200. package/lib/programmatic/bearer-auth.js +176 -0
  201. package/lib/programmatic/builder-api-request.js +175 -0
  202. package/lib/programmatic/builder-api-route-dispatch.js +82 -0
  203. package/lib/programmatic/builder-api-runtime-config.js +42 -0
  204. package/lib/programmatic/builder-api-server.js +230 -0
  205. package/lib/programmatic/builder-help-authentication-catalog.js +282 -0
  206. package/lib/programmatic/builder-help-connected-system-ui.js +266 -0
  207. package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +60 -0
  208. package/lib/programmatic/builder-help-envelope.js +36 -0
  209. package/lib/programmatic/builder-help-fixtures.js +200 -0
  210. package/lib/programmatic/builder-help-governance.js +172 -0
  211. package/lib/programmatic/builder-help-schema-catalog.js +92 -0
  212. package/lib/programmatic/builder-help.js +361 -0
  213. package/lib/programmatic/capability-registry.js +77 -0
  214. package/lib/programmatic/command-options.js +32 -0
  215. package/lib/programmatic/enterprise-mcp-client.js +98 -0
  216. package/lib/programmatic/http-problem-details.js +99 -0
  217. package/lib/programmatic/http-route-match.js +40 -0
  218. package/lib/programmatic/idempotency-cache.js +69 -0
  219. package/lib/programmatic/index.js +53 -0
  220. package/lib/programmatic/integration-path-scope.js +39 -0
  221. package/lib/programmatic/log-redaction.js +66 -0
  222. package/lib/programmatic/manifest-get-section.js +146 -0
  223. package/lib/programmatic/manifest-patch-section.js +252 -0
  224. package/lib/programmatic/manifest-repair.js +122 -0
  225. package/lib/programmatic/manifest-section-dependency-graph.js +160 -0
  226. package/lib/programmatic/manifest-section-io.js +225 -0
  227. package/lib/programmatic/manifest-section-quality.js +171 -0
  228. package/lib/programmatic/manifest-section-registry-data.js +269 -0
  229. package/lib/programmatic/manifest-section-registry.js +205 -0
  230. package/lib/programmatic/nest-http-bridge.js +34 -0
  231. package/lib/programmatic/openapi-component-schemas.js +68 -0
  232. package/lib/programmatic/openapi-description-enricher.js +117 -0
  233. package/lib/programmatic/openapi-descriptions.js +190 -0
  234. package/lib/programmatic/openapi-docs-serving.js +123 -0
  235. package/lib/programmatic/openapi-help-schemas.js +63 -0
  236. package/lib/programmatic/openapi-manifest-schemas.js +141 -0
  237. package/lib/programmatic/openapi-operation-bindings.js +274 -0
  238. package/lib/programmatic/openapi-request-schemas.js +155 -0
  239. package/lib/programmatic/openapi-result-schemas.js +476 -0
  240. package/lib/programmatic/openapi-schema-descriptions-certify.js +139 -0
  241. package/lib/programmatic/openapi-schema-descriptions-core.js +159 -0
  242. package/lib/programmatic/openapi-schema-descriptions-help.js +40 -0
  243. package/lib/programmatic/openapi-schema-descriptions-manifest.js +143 -0
  244. package/lib/programmatic/openapi-schema-descriptions-mcp.js +118 -0
  245. package/lib/programmatic/openapi-schema-descriptions-operations.js +281 -0
  246. package/lib/programmatic/openapi-schema-helpers.js +93 -0
  247. package/lib/programmatic/openapi-spec.js +203 -0
  248. package/lib/programmatic/orchestration-openapi-meta.js +159 -0
  249. package/lib/programmatic/platform-rbac-catalog.js +90 -0
  250. package/lib/programmatic/request-timeout.js +53 -0
  251. package/lib/programmatic/route-handler-map.js +143 -0
  252. package/lib/programmatic/route-handlers-context.js +29 -0
  253. package/lib/programmatic/route-handlers.js +33 -0
  254. package/lib/programmatic/run-certify.js +57 -0
  255. package/lib/programmatic/run-enterprise-mcp.js +32 -0
  256. package/lib/programmatic/run-lifecycle.js +29 -0
  257. package/lib/programmatic/run-repair.js +41 -0
  258. package/lib/programmatic/run-repository-emcp.js +54 -0
  259. package/lib/programmatic/run-template-workspace.js +61 -0
  260. package/lib/programmatic/run-test.js +49 -0
  261. package/lib/programmatic/run-upload.js +63 -0
  262. package/lib/programmatic/run-validate.js +53 -0
  263. package/lib/programmatic/run-wizard.js +40 -0
  264. package/lib/programmatic/system-key.js +48 -0
  265. package/lib/programmatic/wizard-config-normalize.js +118 -0
  266. package/lib/programmatic/workspace-cleanup.js +128 -0
  267. package/lib/programmatic/workspace-context.js +145 -0
  268. package/lib/programmatic/workspace-lock.js +75 -0
  269. package/lib/programmatic/workspace-meta.js +64 -0
  270. package/lib/programmatic/workspace-ops.js +149 -0
  271. package/lib/protection/run-commands-upload-sync.js +35 -0
  272. package/lib/protection/run-commands.js +2 -20
  273. package/lib/protection/upload-batch.js +11 -7
  274. package/lib/resolvers/resource-type-file.js +22 -0
  275. package/lib/schema/external-datasource.schema.json +161 -1
  276. package/lib/schema/external-system.schema.json +242 -4
  277. package/lib/schema/flag-map-validation-run.json +3 -0
  278. package/lib/schema/type/credential-field-registry.json +2 -2
  279. package/lib/schema/type/document-storage.json +9 -0
  280. package/lib/utils/agent-trust-run-display.js +1 -1
  281. package/lib/utils/app-path-resolve.js +168 -0
  282. package/lib/utils/authentication-env-template.js +222 -0
  283. package/lib/utils/cli-sync-options.js +7 -11
  284. package/lib/utils/cli-utils.js +4 -0
  285. package/lib/utils/cli-workspace-options.js +29 -0
  286. package/lib/utils/compose-generate-docker-compose.js +102 -0
  287. package/lib/utils/configuration-env-resolver.js +203 -27
  288. package/lib/utils/datasource-test-run-certificate-tty.js +28 -2
  289. package/lib/utils/datasource-test-run-certification-debug.js +130 -0
  290. package/lib/utils/datasource-test-run-debug-display.js +2 -0
  291. package/lib/utils/datasource-test-run-debug-slice.js +7 -0
  292. package/lib/utils/datasource-test-run-display.js +2 -1
  293. package/lib/utils/dev-init-ssh-merge.js +165 -23
  294. package/lib/utils/dev-pin-share-instructions.js +110 -0
  295. package/lib/utils/dev-ssh-config-helper.js +177 -33
  296. package/lib/utils/enterprise-knowledge-paths.js +142 -0
  297. package/lib/utils/env-copy.js +2 -1
  298. package/lib/utils/env-dotenv-format.js +103 -0
  299. package/lib/utils/external-env-template.js +4 -1
  300. package/lib/utils/external-system-display.js +5 -1
  301. package/lib/utils/external-system-readiness-compact-blocks.js +221 -0
  302. package/lib/utils/external-system-readiness-compact-display.js +324 -0
  303. package/lib/utils/external-system-readiness-core.js +16 -0
  304. package/lib/utils/external-system-readiness-deploy-display.js +29 -11
  305. package/lib/utils/external-system-readiness-display-internals.js +3 -1
  306. package/lib/utils/external-system-readiness-display.js +20 -30
  307. package/lib/utils/external-system-system-test-tty-certification.js +135 -0
  308. package/lib/utils/external-system-system-test-tty.js +5 -41
  309. package/lib/utils/help-builder.js +6 -1
  310. package/lib/utils/host-ops-job-labels.js +236 -0
  311. package/lib/utils/host-ops-job-result.js +61 -0
  312. package/lib/utils/host-ops-poll-ui.js +163 -0
  313. package/lib/utils/integration-path-resolve.js +112 -0
  314. package/lib/utils/paths-reload-source.js +148 -0
  315. package/lib/utils/paths-system-builder-keys.js +25 -0
  316. package/lib/utils/paths.js +151 -26
  317. package/lib/utils/runtime-configuration-values.js +64 -2
  318. package/lib/utils/secrets-helpers.js +5 -1
  319. package/lib/utils/ssh-known-hosts.js +130 -0
  320. package/lib/utils/test-target-cli-options.js +75 -0
  321. package/lib/utils/upload-sync-options.js +4 -0
  322. package/lib/utils/validation-run-request.js +11 -2
  323. package/lib/validation/external-manifest-validator.js +11 -1
  324. package/lib/validation/openapi-contract-surface-validator.js +426 -0
  325. package/lib/validation/openapi-contract-vendor-validation.js +238 -0
  326. package/lib/validation/validate-external-file.js +160 -0
  327. package/lib/validation/validate.js +6 -117
  328. package/package.json +46 -29
  329. package/packages/builder-api/README.md +120 -0
  330. package/packages/builder-api/package.json +30 -0
  331. package/packages/builder-api/src/app.module.ts +7 -0
  332. package/packages/builder-api/src/auth/auth.module.ts +5 -0
  333. package/packages/builder-api/src/external-system/external-system.module.ts +5 -0
  334. package/packages/builder-api/src/main.ts +16 -0
  335. package/packages/builder-api/tsconfig.json +15 -0
  336. package/scripts/check-builder-api-architecture-grep.js +79 -0
  337. package/scripts/docker_image_content_gate.js +47 -0
  338. package/scripts/docker_image_content_gate.sh +5 -0
  339. package/scripts/export-builder-api-evidence.js +262 -0
  340. package/scripts/lib/builder-api-docker-staging-copy.js +60 -0
  341. package/scripts/lib/builder-api-docker-staging-core.js +183 -0
  342. package/scripts/lib/docker-image-content-gate-core.js +166 -0
  343. package/scripts/lib/plan-actor.js +109 -0
  344. package/scripts/lib/plan-config.js +20 -0
  345. package/scripts/lib/plan-gates.js +154 -0
  346. package/scripts/lib/plan-meta.js +104 -0
  347. package/scripts/lib/plan-parse.js +162 -0
  348. package/scripts/lib/plan-prevalidate-gates.js +326 -0
  349. package/scripts/lib/plan-run-core.js +125 -0
  350. package/scripts/lib/plan-run-report.js +108 -0
  351. package/scripts/plan-approve.js +24 -0
  352. package/scripts/plan-archive.js +49 -0
  353. package/scripts/plan-run.js +13 -0
  354. package/scripts/plan-set-actor.js +21 -0
  355. package/scripts/plan-validate.js +80 -0
  356. package/scripts/pnpm/af.mjs +110 -0
  357. package/scripts/pnpm/check-quiet.mjs +32 -0
  358. package/scripts/pnpm/dev-install.mjs +29 -0
  359. package/scripts/pnpm/dev-reload.mjs +18 -0
  360. package/scripts/pnpm/help.mjs +55 -0
  361. package/scripts/pnpm/lib/dev-reload-core.mjs +189 -0
  362. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +67 -0
  363. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +32 -0
  364. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +124 -0
  365. package/scripts/pnpm/lib/resolve-app-url.mjs +183 -0
  366. package/scripts/pnpm/lib/spinner.mjs +42 -0
  367. package/scripts/stage_builder_api_for_docker.js +65 -0
  368. package/scripts/sync-builder-api-template.js +44 -0
  369. package/templates/README.md +3 -0
  370. package/templates/applications/builder-api/Dockerfile +38 -0
  371. package/templates/applications/builder-api/application.yaml +75 -0
  372. package/templates/applications/builder-api/env.template +48 -0
  373. package/templates/applications/dataplane/application.yaml +1 -1
  374. package/templates/applications/dataplane/env.template +10 -6
  375. package/templates/applications/dataplane/rbac.yaml +21 -1
  376. package/templates/applications/miso-controller/application.yaml +1 -1
  377. package/templates/applications/miso-controller/env.template +1 -3
  378. package/templates/channels/README.md +18 -0
  379. package/templates/channels/ai-fabrix-icon.jpg +0 -0
  380. package/templates/channels/chatgpt/install.template.md.hbs +21 -24
  381. package/templates/channels/chatgpt/integration/README.md +11 -0
  382. package/templates/channels/chatgpt/integration/application.json +15 -0
  383. package/templates/channels/chatgpt/integration/chatgpt-system.json +29 -0
  384. package/templates/channels/chatgpt/integration/env.template +4 -0
  385. package/templates/channels/microsoft-copilot/install.template.md.hbs +20 -25
  386. package/templates/channels/microsoft-copilot/integration/README.md +11 -0
  387. package/templates/channels/microsoft-copilot/integration/application.json +15 -0
  388. package/templates/channels/microsoft-copilot/integration/env.template +6 -0
  389. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +34 -0
  390. package/templates/channels/slack/install.template.md.hbs +48 -18
  391. package/templates/channels/slack/integration/README.md +38 -0
  392. package/templates/channels/slack/integration/application.json +18 -0
  393. package/templates/channels/slack/integration/env.template +29 -0
  394. package/templates/channels/slack/integration/slack-datasource-conversations.json +226 -0
  395. package/templates/channels/slack/integration/slack-datasource-messages.json +269 -0
  396. package/templates/channels/slack/integration/slack-system.json +86 -0
  397. package/templates/channels/slack/manifest.template.json.hbs +20 -10
  398. package/templates/channels/teams-bot/install.template.md.hbs +31 -17
  399. package/templates/channels/teams-bot/integration/README.md +18 -0
  400. package/templates/channels/teams-bot/integration/application.json +17 -0
  401. package/templates/channels/teams-bot/integration/env.template +5 -0
  402. package/templates/channels/teams-bot/integration/teams-bot-system.json +55 -0
  403. package/templates/channels/teams-bot/manifest.template.json.hbs +27 -0
  404. package/templates/channels/web-embed/install.template.md.hbs +16 -26
  405. package/templates/channels/web-embed/integration/README.md +11 -0
  406. package/templates/channels/web-embed/integration/application.json +15 -0
  407. package/templates/channels/web-embed/integration/env.template +4 -0
  408. package/templates/channels/web-embed/integration/web-embed-system.json +29 -0
  409. package/templates/openapi/redoc_page.html +86 -0
  410. package/templates/python/docker-compose.hbs +11 -0
  411. package/templates/python/requirements.txt +1 -1
  412. package/templates/typescript/docker-compose.hbs +11 -0
  413. package/lib/certification/cli-cert-sync-skip.js +0 -21
@@ -2,10 +2,17 @@
2
2
  * @fileoverview After dev init: merge SSH config Host alias from sync settings / server URL.
3
3
  */
4
4
 
5
+ const path = require('path');
5
6
  const chalk = require('chalk');
6
7
  const config = require('../core/config');
7
8
  const logger = require('./logger');
8
- const { ensureDevSshConfigBlock } = require('./dev-ssh-config-helper');
9
+ const { ensureDevSshConfigBlock, listHostAliasesForUserHost } = require('./dev-ssh-config-helper');
10
+ const { ensureSshDir } = require('./ssh-key-helper');
11
+ const { nodeFs } = require('../internal/node-fs');
12
+ const {
13
+ collectDevSshKnownHostCandidates,
14
+ refreshDevSshKnownHosts
15
+ } = require('./ssh-known-hosts');
9
16
  const { successGlyph } = require('./cli-layout-chalk');
10
17
 
11
18
  /**
@@ -24,6 +31,134 @@ function hostnameFromBaseUrl(baseUrl) {
24
31
  }
25
32
  }
26
33
 
34
+ /**
35
+ * @param {string} sshDir
36
+ * @returns {Promise<string>}
37
+ */
38
+ async function readSshConfigContent(sshDir) {
39
+ const configPath = path.join(ensureSshDir(sshDir), 'config');
40
+ try {
41
+ return await nodeFs().promises.readFile(configPath, 'utf8');
42
+ } catch (e) {
43
+ if (e.code === 'ENOENT') {
44
+ return '';
45
+ }
46
+ throw e;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @param {Object} p
52
+ * @param {string} p.baseUrl
53
+ * @param {string} p.syncHost
54
+ * @param {string} p.syncUser
55
+ * @param {string} p.hostAlias
56
+ * @param {string} p.existingSshConfig
57
+ * @param {string|number} [p.developerId]
58
+ * @param {string} [p.developerRootDomain]
59
+ * @returns {Promise<void>}
60
+ */
61
+ async function refreshKnownHostsForDevSsh(p) {
62
+ const legacyHostAliases = listHostAliasesForUserHost(
63
+ p.existingSshConfig,
64
+ p.syncHost,
65
+ p.syncUser
66
+ );
67
+ const candidates = collectDevSshKnownHostCandidates({
68
+ syncHost: p.syncHost,
69
+ syncUser: p.syncUser,
70
+ hostAlias: p.hostAlias,
71
+ baseUrl: p.baseUrl,
72
+ developerId: p.developerId,
73
+ developerRootDomain: p.developerRootDomain,
74
+ legacyHostAliases
75
+ });
76
+ const { cleared, failed } = await refreshDevSshKnownHosts(candidates);
77
+ if (cleared.length > 0) {
78
+ logger.log(
79
+ `${chalk.green(' ')}${successGlyph()}${chalk.green(' Cleared stale SSH host keys for ')}` +
80
+ chalk.cyan(cleared.join(', ')) +
81
+ chalk.gray(' (accept the new key on first connect if prompted).')
82
+ );
83
+ }
84
+ if (failed.some((row) => row.error === 'ssh-keygen not found')) {
85
+ logger.log(
86
+ chalk.yellow(
87
+ ' ⚠ ssh-keygen not found; if SSH reports "REMOTE HOST IDENTIFICATION HAS CHANGED", ' +
88
+ `run: ssh-keygen -R ${p.syncHost}`
89
+ )
90
+ );
91
+ }
92
+ }
93
+
94
+ /**
95
+ * @param {string|undefined} developerRootDomain
96
+ * @returns {string|undefined}
97
+ */
98
+ function trimDeveloperRootDomain(developerRootDomain) {
99
+ const trimmed = developerRootDomain && String(developerRootDomain).trim();
100
+ return trimmed || undefined;
101
+ }
102
+
103
+ /**
104
+ * @param {Object} res
105
+ * @param {string} syncUser
106
+ * @param {string} syncHost
107
+ * @returns {void}
108
+ */
109
+ function logSshConfigMergeResult(res, syncUser, syncHost) {
110
+ if (res.skippedDuplicate) {
111
+ logger.log(
112
+ chalk.gray(' SSH config already has ') +
113
+ chalk.cyan(`${syncUser}@${syncHost}`) +
114
+ chalk.gray(` (Host ${res.hostAlias}); left unchanged.`)
115
+ );
116
+ return;
117
+ }
118
+ const prefix =
119
+ `${chalk.green(' ')}${successGlyph()}${chalk.green(' SSH config updated: ')}` +
120
+ chalk.cyan(`Host ${res.hostAlias}`);
121
+ const suffix = res.migratedFrom
122
+ ? chalk.gray(` (was ${res.migratedFrom}) → ${res.configPath}`)
123
+ : chalk.gray(` → ${res.configPath}`);
124
+ logger.log(prefix + suffix);
125
+ }
126
+
127
+ /**
128
+ * @param {Object} p
129
+ * @param {string} p.baseUrl
130
+ * @param {string} p.devId
131
+ * @param {string} p.syncUser
132
+ * @param {string} p.syncHost
133
+ * @param {string|undefined} p.rootTrimmed
134
+ * @returns {Promise<{ hostAlias: string|null, syncUser: string, syncHost: string }>}
135
+ */
136
+ async function mergeDevSshConfigBlock(p) {
137
+ const sshDir = ensureSshDir();
138
+ const existingSshConfig = await readSshConfigContent(sshDir);
139
+ const res = await ensureDevSshConfigBlock(p.syncUser, p.syncHost, sshDir, {
140
+ developerId: p.devId,
141
+ developerRootDomain: p.rootTrimmed
142
+ });
143
+ if (!res.ok || !res.configPath || !res.hostAlias) {
144
+ if (!res.ok && res.error) {
145
+ logger.log(chalk.yellow(` ⚠ Could not update SSH config: ${res.error}`));
146
+ }
147
+ return { hostAlias: null, syncUser: p.syncUser, syncHost: p.syncHost };
148
+ }
149
+ await refreshKnownHostsForDevSsh({
150
+ baseUrl: p.baseUrl,
151
+ syncHost: p.syncHost,
152
+ syncUser: p.syncUser,
153
+ hostAlias: res.hostAlias,
154
+ existingSshConfig,
155
+ developerId: p.devId,
156
+ developerRootDomain: p.rootTrimmed
157
+ });
158
+ logSshConfigMergeResult(res, p.syncUser, p.syncHost);
159
+ return { hostAlias: res.hostAlias, syncUser: p.syncUser, syncHost: p.syncHost };
160
+ }
161
+
27
162
  /**
28
163
  * Update ~/.ssh/config with Host <user>.<host> for interactive SSH; log result.
29
164
  * @param {string} baseUrl - Builder Server base URL
@@ -37,30 +172,37 @@ async function mergeDevSshConfigAfterInit(baseUrl, devId) {
37
172
  return { hostAlias: null, syncUser, syncHost: null };
38
173
  }
39
174
  try {
40
- const res = await ensureDevSshConfigBlock(syncUser, syncHost);
41
- if (res.ok && res.configPath && res.hostAlias) {
42
- if (res.skippedDuplicate) {
43
- logger.log(
44
- chalk.gray(' SSH config already has ') +
45
- chalk.cyan(`${syncUser}@${syncHost}`) +
46
- chalk.gray(` (Host ${res.hostAlias}); left unchanged.`)
47
- );
48
- } else {
49
- logger.log(
50
- `${chalk.green(' ')}${successGlyph()}${chalk.green(' SSH config updated: ')}` +
51
- chalk.cyan(`Host ${res.hostAlias}`) +
52
- chalk.gray(` → ${res.configPath}`)
53
- );
54
- }
55
- return { hostAlias: res.hostAlias, syncUser, syncHost };
56
- }
57
- if (!res.ok && res.error) {
58
- logger.log(chalk.yellow(` ⚠ Could not update SSH config: ${res.error}`));
59
- }
175
+ const rootTrimmed = trimDeveloperRootDomain(await config.getDeveloperRootDomain());
176
+ return await mergeDevSshConfigBlock({
177
+ baseUrl,
178
+ devId,
179
+ syncUser,
180
+ syncHost,
181
+ rootTrimmed
182
+ });
60
183
  } catch (e) {
61
184
  logger.log(chalk.yellow(` ⚠ Could not update SSH config: ${e.message || e}`));
185
+ return { hostAlias: null, syncUser, syncHost };
186
+ }
187
+ }
188
+
189
+ /**
190
+ * @param {{ hostAlias: string|null }} sshInfo
191
+ * @returns {void}
192
+ */
193
+ function logDevSshConnectHint(sshInfo) {
194
+ if (!sshInfo || !sshInfo.hostAlias) {
195
+ return;
62
196
  }
63
- return { hostAlias: null, syncUser, syncHost };
197
+ logger.log(
198
+ chalk.gray(' SSH: ') +
199
+ chalk.cyan(`ssh ${sshInfo.hostAlias}`) +
200
+ chalk.gray(' (host keys refreshed if the server was reprovisioned).')
201
+ );
64
202
  }
65
203
 
66
- module.exports = { mergeDevSshConfigAfterInit, hostnameFromBaseUrl };
204
+ module.exports = {
205
+ mergeDevSshConfigAfterInit,
206
+ logDevSshConnectHint,
207
+ hostnameFromBaseUrl
208
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Copy-paste onboarding block for `aifabrix dev pin` (admin → developer).
3
+ *
4
+ * @fileoverview Dev PIN share instructions with platform prerequisites
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('./logger');
13
+ const { formatSuccessLine } = require('./cli-test-layout-chalk');
14
+
15
+ const NODE_INSTALL_URL = 'https://nodejs.org/';
16
+ const BUILDER_INSTALL_CMD = 'npm install -g @aifabrix/builder';
17
+
18
+ /**
19
+ * @returns {void}
20
+ */
21
+ function logDevPinPrerequisites() {
22
+ logger.log(chalk.bold('Prerequisites (once per machine):'));
23
+ logger.log('');
24
+ logger.log(chalk.cyan('Windows'));
25
+ logger.log(chalk.gray(` 1. Install Node.js 18+ from ${NODE_INSTALL_URL}`));
26
+ logger.log(` 2. ${BUILDER_INSTALL_CMD}`);
27
+ logger.log('');
28
+ logger.log(chalk.cyan('Linux & macOS'));
29
+ logger.log(
30
+ chalk.gray(` 1. Install Node.js 18+ (${NODE_INSTALL_URL} or your package manager)`)
31
+ );
32
+ logger.log(` 2. ${BUILDER_INSTALL_CMD}`);
33
+ logger.log('');
34
+ logger.log(chalk.gray(' After install, `af` is a shortcut for `aifabrix`.'));
35
+ }
36
+
37
+ /**
38
+ * @param {string} baseInit
39
+ * @param {string} id
40
+ * @param {string} [hostsIp]
41
+ * @returns {void}
42
+ */
43
+ function logPinHostsVariantLines(baseInit, id, hostsIp) {
44
+ logger.log(
45
+ chalk.cyan('2) Hosts file / administrator')
46
+ + chalk.gray(
47
+ ' — use when the hostname does not resolve and your organisation does not publish DNS for it. '
48
+ + 'Run in an elevated terminal so the hosts file can be updated, or add the entry manually:'
49
+ )
50
+ );
51
+ if (hostsIp) {
52
+ logger.log(` ${baseInit} --add-hosts --hosts-ip ${hostsIp}`);
53
+ } else {
54
+ logger.log(` ${baseInit} --add-hosts --hosts-ip <server-LAN-IPv4>`);
55
+ logger.log(
56
+ chalk.gray(
57
+ ` Replace <server-LAN-IPv4> with the Builder Server IPv4, or re-run: aifabrix dev pin ${id} --hosts-ip <ip>`
58
+ )
59
+ );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Log copy-paste dev init commands for the developer (standard vs hosts-file).
65
+ * @param {Object} p
66
+ * @param {string} p.id - Developer ID
67
+ * @param {string} p.serverUrl - Builder Server base URL
68
+ * @param {string} p.pin - One-time PIN
69
+ * @param {string} p.expiresAt - PIN expiry (API string)
70
+ * @param {string} [p.hostsIp] - Optional IPv4 for the hosts variant
71
+ * @returns {void}
72
+ */
73
+ function logDevPinShareInstructions(p) {
74
+ const { id, serverUrl, pin, expiresAt, hostsIp } = p;
75
+ const baseInit = `aifabrix dev init --developer-id ${id} --server ${serverUrl} --pin ${pin}`;
76
+ const pinOnly = `aifabrix dev init --pin ${pin}`;
77
+ logger.log(formatSuccessLine(`PIN created for ${id}, expires ${expiresAt}.`));
78
+ logger.log('');
79
+ logger.log(
80
+ chalk.bold('Share with the developer — prerequisites and onboarding (PIN is one-time):')
81
+ );
82
+ logger.log('');
83
+ logDevPinPrerequisites();
84
+ logger.log('');
85
+ logger.log(chalk.bold('Then run one command to connect (choose the variant that fits):'));
86
+ logger.log('');
87
+ logger.log(
88
+ chalk.cyan('1) Standard')
89
+ + chalk.gray(' — use when this machine can resolve the server hostname (DNS or hosts already set):')
90
+ );
91
+ logger.log(` ${baseInit}`);
92
+ logger.log('');
93
+ logPinHostsVariantLines(baseInit, id, hostsIp);
94
+ logger.log('');
95
+ logger.log(
96
+ chalk.cyan('3) Config already has remote-server + developer-id')
97
+ + chalk.gray(
98
+ ' — e.g. second machine / SSH session after settings were merged; same onboarding, PIN only:'
99
+ )
100
+ );
101
+ logger.log(` ${pinOnly}`);
102
+ logger.log('');
103
+ }
104
+
105
+ module.exports = {
106
+ BUILDER_INSTALL_CMD,
107
+ NODE_INSTALL_URL,
108
+ logDevPinPrerequisites,
109
+ logDevPinShareInstructions
110
+ };
@@ -6,18 +6,49 @@
6
6
 
7
7
  const nodeFsLib = require('../internal/node-fs');
8
8
  const path = require('path');
9
- const { ensureSshDir } = require('./ssh-key-helper');
9
+ const { ensureSshDir, getDefaultEd25519PrivateKeyPath } = require('./ssh-key-helper');
10
10
 
11
11
  /**
12
- * Derive SSH config Host alias: sync user + real hostname (e.g. dev01.builder02.local).
13
- * @param {string} sshUser - sync-ssh-user
14
- * @param {string} sshHost - sync-ssh-host (HostName)
12
+ * Legacy SSH Host alias: sync user + builder API hostname (e.g. dev03.builder01.aifabrix.dev).
13
+ * @param {string} sshUser
14
+ * @param {string} sshHost
15
15
  * @returns {string}
16
16
  */
17
17
  function getDevSshHostAlias(sshUser, sshHost) {
18
18
  return `${sshUser}.${sshHost}`;
19
19
  }
20
20
 
21
+ /**
22
+ * @param {string|number} developerId
23
+ * @returns {string}
24
+ */
25
+ function formatDeveloperNumericId(developerId) {
26
+ const trimmed = String(developerId ?? '').trim();
27
+ if (/^\d+$/.test(trimmed)) {
28
+ return String(parseInt(trimmed, 10)).padStart(2, '0');
29
+ }
30
+ return trimmed.replace(/^dev/i, '');
31
+ }
32
+
33
+ /**
34
+ * Preferred SSH config Host alias — matches per-developer app host when developer-root-domain is set.
35
+ *
36
+ * @param {Object} p
37
+ * @param {string} p.syncUser
38
+ * @param {string} p.syncHost - sync-ssh-host (HostName for TCP connect)
39
+ * @param {string|number} [p.developerId]
40
+ * @param {string} [p.developerRootDomain] - e.g. aifabrix.dev → dev03.aifabrix.dev; dev.aifabrix → dev03.dev.aifabrix
41
+ * @returns {string}
42
+ */
43
+ function resolveDevSshHostAlias(p) {
44
+ const root = String(p.developerRootDomain || '').trim().replace(/^\.+|\.+$/g, '');
45
+ const id = p.developerId !== undefined && p.developerId !== null ? String(p.developerId).trim() : '';
46
+ if (root && id) {
47
+ return `dev${formatDeveloperNumericId(id)}.${root}`;
48
+ }
49
+ return getDevSshHostAlias(p.syncUser, p.syncHost);
50
+ }
51
+
21
52
  /**
22
53
  * Parse ssh config into Host blocks (lines before the first Host are ignored).
23
54
  * @param {string} content
@@ -91,6 +122,48 @@ function resolveConnectAlias(matchedBlock, canonicalAlias) {
91
122
  return matchedBlock.aliases[0] || canonicalAlias;
92
123
  }
93
124
 
125
+ /**
126
+ * @param {string[]} body
127
+ * @param {string} hostname
128
+ * @param {string} user
129
+ * @returns {boolean}
130
+ */
131
+ function isSshHostBlockForUserHost(body, hostname, user) {
132
+ return (
133
+ getHostBlockDirective(body, 'HostName') === hostname &&
134
+ getHostBlockDirective(body, 'User') === user
135
+ );
136
+ }
137
+
138
+ /**
139
+ * Drop Host blocks that target the same HostName + User (legacy alias migration).
140
+ * @param {string} content
141
+ * @param {string} hostname
142
+ * @param {string} user
143
+ * @returns {string}
144
+ */
145
+ function removeSshHostBlocksForUserHost(content, hostname, user) {
146
+ const lines = (content || '').split(/\r?\n/);
147
+ const out = [];
148
+ for (let i = 0; i < lines.length; i++) {
149
+ const m = lines[i].match(/^Host\s+(.+?)\s*$/i);
150
+ if (!m) {
151
+ out.push(lines[i]);
152
+ continue;
153
+ }
154
+ const body = [];
155
+ i++;
156
+ while (i < lines.length && !/^Host\s/i.test(lines[i])) {
157
+ body.push(lines[i++]);
158
+ }
159
+ if (!isSshHostBlockForUserHost(body, hostname, user)) {
160
+ out.push(`Host ${m[1].trim()}`, ...body);
161
+ }
162
+ i--;
163
+ }
164
+ return out.join('\n').replace(/\s+$/, '');
165
+ }
166
+
94
167
  /**
95
168
  * Replace an existing Host block for hostAlias, or append a new block at EOF.
96
169
  * @param {string} content - Existing ssh config (may be empty)
@@ -100,7 +173,8 @@ function resolveConnectAlias(matchedBlock, canonicalAlias) {
100
173
  * @returns {string} Updated config
101
174
  */
102
175
  function upsertSshHostBlock(content, hostAlias, hostname, user) {
103
- const lines = (content || '').split(/\r?\n/);
176
+ const withoutDupes = removeSshHostBlocksForUserHost(content, hostname, user);
177
+ const lines = (withoutDupes || '').split(/\r?\n/);
104
178
  const out = [];
105
179
  let i = 0;
106
180
  while (i < lines.length) {
@@ -123,6 +197,7 @@ function upsertSshHostBlock(content, hostAlias, hostname, user) {
123
197
  `Host ${hostAlias}`,
124
198
  ` HostName ${hostname}`,
125
199
  ` User ${user}`,
200
+ ` IdentityFile ${getDefaultEd25519PrivateKeyPath().replace(/\\/g, '/')}`,
126
201
  ' IdentitiesOnly yes'
127
202
  ].join('\n');
128
203
  const trimmed = out.join('\n').replace(/\s+$/, '');
@@ -130,12 +205,27 @@ function upsertSshHostBlock(content, hostAlias, hostname, user) {
130
205
  return `${prefix}${block}\n`;
131
206
  }
132
207
 
208
+ function listHostAliasesForUserHost(content, hostname, user) {
209
+ const aliases = [];
210
+ for (const block of parseSshHostBlocks(content)) {
211
+ const hn = getHostBlockDirective(block.body, 'HostName');
212
+ const u = getHostBlockDirective(block.body, 'User');
213
+ if (hn === hostname && u === user) {
214
+ aliases.push(...block.aliases);
215
+ }
216
+ }
217
+ return aliases;
218
+ }
219
+
133
220
  /**
134
221
  * @param {string} sshUser
135
222
  * @param {string} sshHost
136
- * @returns {{ error: string }|{ user: string, host: string, hostAlias: string }}
223
+ * @param {Object} [options]
224
+ * @param {string|number} [options.developerId]
225
+ * @param {string} [options.developerRootDomain]
226
+ * @returns {{ error: string }|{ user: string, host: string, hostAlias: string, legacyHostAlias: string }}
137
227
  */
138
- function parseEnsureSshInputs(sshUser, sshHost) {
228
+ function parseEnsureSshInputs(sshUser, sshHost, options = {}) {
139
229
  if (!sshUser || typeof sshUser !== 'string' || !sshUser.trim()) {
140
230
  return { error: 'missing ssh user' };
141
231
  }
@@ -144,7 +234,53 @@ function parseEnsureSshInputs(sshUser, sshHost) {
144
234
  }
145
235
  const user = sshUser.trim();
146
236
  const host = sshHost.trim();
147
- return { user, host, hostAlias: getDevSshHostAlias(user, host) };
237
+ const hostAlias = resolveDevSshHostAlias({
238
+ syncUser: user,
239
+ syncHost: host,
240
+ developerId: options.developerId,
241
+ developerRootDomain: options.developerRootDomain
242
+ });
243
+ return {
244
+ user,
245
+ host,
246
+ hostAlias,
247
+ legacyHostAlias: getDevSshHostAlias(user, host)
248
+ };
249
+ }
250
+
251
+ /**
252
+ * @param {string} configPath
253
+ * @returns {Promise<{ content: string }|{ error: string }>}
254
+ */
255
+ async function readExistingSshConfig(configPath) {
256
+ try {
257
+ const content = await nodeFsLib.nodeFs().promises.readFile(configPath, 'utf8');
258
+ return { content };
259
+ } catch (e) {
260
+ if (e.code === 'ENOENT') {
261
+ return { content: '' };
262
+ }
263
+ return { error: e.message || String(e) };
264
+ }
265
+ }
266
+
267
+ /**
268
+ * @param {{ aliases: string[], body: string[] }|null} matched
269
+ * @param {string} hostAlias
270
+ * @param {string} rootDomain
271
+ * @returns {{ skip: true, hostAlias: string }|{ skip: false, matched: typeof matched }}
272
+ */
273
+ function resolveExistingSshBlock(matched, hostAlias, rootDomain) {
274
+ if (!matched) {
275
+ return { skip: false, matched: null };
276
+ }
277
+ const matchedAlias = resolveConnectAlias(matched, hostAlias);
278
+ const keepExisting =
279
+ !rootDomain || (matched.aliases.includes(hostAlias) && matchedAlias === hostAlias);
280
+ if (keepExisting) {
281
+ return { skip: true, hostAlias: matchedAlias };
282
+ }
283
+ return { skip: false, matched };
148
284
  }
149
285
 
150
286
  /**
@@ -152,44 +288,52 @@ function parseEnsureSshInputs(sshUser, sshHost) {
152
288
  * @param {string} sshUser - SSH user (sync-ssh-user)
153
289
  * @param {string} sshHost - Real hostname (sync-ssh-host)
154
290
  * @param {string} [sshDir] - SSH directory (default ~/.ssh)
155
- * @returns {Promise<{ ok: boolean, configPath?: string, hostAlias?: string, error?: string, skippedDuplicate?: boolean }>}
291
+ * @param {Object} [options]
292
+ * @param {string|number} [options.developerId]
293
+ * @param {string} [options.developerRootDomain]
294
+ * @returns {Promise<{ ok: boolean, configPath?: string, hostAlias?: string, error?: string, skippedDuplicate?: boolean, updated?: boolean }>}
156
295
  */
157
- async function ensureDevSshConfigBlock(sshUser, sshHost, sshDir) {
158
- const parsed = parseEnsureSshInputs(sshUser, sshHost);
296
+ async function ensureDevSshConfigBlock(sshUser, sshHost, sshDir, options = {}) {
297
+ const parsed = parseEnsureSshInputs(sshUser, sshHost, options);
159
298
  if ('error' in parsed) {
160
299
  return { ok: false, error: parsed.error };
161
300
  }
162
- const { user, host, hostAlias } = parsed;
163
- const dir = ensureSshDir(sshDir);
164
- const configPath = path.join(dir, 'config');
165
- let existing = '';
166
- try {
167
- existing = await nodeFsLib.nodeFs().promises.readFile(configPath, 'utf8');
168
- } catch (e) {
169
- if (e.code !== 'ENOENT') {
170
- return { ok: false, error: e.message || String(e) };
171
- }
301
+ const { user, host, hostAlias, legacyHostAlias } = parsed;
302
+ const configPath = path.join(ensureSshDir(sshDir), 'config');
303
+ const readResult = await readExistingSshConfig(configPath);
304
+ if ('error' in readResult) {
305
+ return { ok: false, error: readResult.error };
306
+ }
307
+ const rootDomain = String(options.developerRootDomain || '').trim();
308
+ const matched = findMatchingHostBlockForUserHost(readResult.content, host, user);
309
+ const existing = resolveExistingSshBlock(matched, hostAlias, rootDomain);
310
+ if (existing.skip) {
311
+ return { ok: true, configPath, hostAlias: existing.hostAlias, skippedDuplicate: true };
172
312
  }
173
- const matched = findMatchingHostBlockForUserHost(existing, host, user);
174
- if (matched) {
175
- return {
176
- ok: true,
177
- configPath,
178
- hostAlias: resolveConnectAlias(matched, hostAlias),
179
- skippedDuplicate: true
180
- };
181
- }
182
- const next = upsertSshHostBlock(existing, hostAlias, host, user);
183
- if (next === existing) {
313
+ const next = upsertSshHostBlock(readResult.content, hostAlias, host, user);
314
+ if (next === readResult.content) {
184
315
  return { ok: true, configPath, hostAlias };
185
316
  }
186
317
  await nodeFsLib.nodeFs().promises.writeFile(configPath, next, { mode: 0o600 });
187
- return { ok: true, configPath, hostAlias };
318
+ const migratedFrom = existing.matched
319
+ ? resolveConnectAlias(existing.matched, hostAlias)
320
+ : legacyHostAlias;
321
+ return {
322
+ ok: true,
323
+ configPath,
324
+ hostAlias,
325
+ updated: true,
326
+ migratedFrom: migratedFrom !== hostAlias ? migratedFrom : undefined
327
+ };
188
328
  }
189
329
 
190
330
  module.exports = {
331
+ formatDeveloperNumericId,
191
332
  getDevSshHostAlias,
333
+ resolveDevSshHostAlias,
192
334
  findMatchingHostBlockForUserHost,
335
+ listHostAliasesForUserHost,
336
+ removeSshHostBlocksForUserHost,
193
337
  resolveConnectAlias,
194
338
  upsertSshHostBlock,
195
339
  ensureDevSshConfigBlock