@useatlas/create 0.0.1 → 0.0.3

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 (298) hide show
  1. package/README.md +4 -18
  2. package/index.ts +191 -31
  3. package/package.json +1 -1
  4. package/templates/docker/.env.example +3 -3
  5. package/templates/docker/Dockerfile.sidecar +28 -0
  6. package/templates/docker/bin/__tests__/plugin-cli.test.ts +11 -11
  7. package/templates/docker/bin/atlas.ts +120 -56
  8. package/templates/docker/data/demo-semantic/catalog.yml +51 -27
  9. package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
  10. package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
  11. package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
  12. package/templates/docker/data/demo-semantic/glossary.yml +104 -8
  13. package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
  14. package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
  15. package/templates/docker/docker-compose.yml +1 -1
  16. package/templates/docker/docs/deploy.md +4 -41
  17. package/templates/docker/package.json +17 -1
  18. package/templates/docker/semantic/catalog.yml +62 -3
  19. package/templates/docker/semantic/entities/accounts.yml +162 -0
  20. package/templates/docker/semantic/entities/companies.yml +143 -0
  21. package/templates/docker/semantic/entities/people.yml +132 -0
  22. package/templates/docker/semantic/glossary.yml +116 -4
  23. package/templates/docker/semantic/metrics/accounts.yml +77 -0
  24. package/templates/docker/semantic/metrics/companies.yml +63 -0
  25. package/templates/docker/sidecar/Dockerfile +5 -6
  26. package/templates/docker/sidecar/railway.json +1 -2
  27. package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
  28. package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
  29. package/templates/docker/src/api/__tests__/health.test.ts +30 -8
  30. package/templates/docker/src/api/routes/admin.ts +549 -8
  31. package/templates/docker/src/api/routes/chat.ts +5 -20
  32. package/templates/docker/src/api/routes/health.ts +39 -27
  33. package/templates/docker/src/api/routes/openapi.ts +1329 -74
  34. package/templates/docker/src/api/routes/query.ts +2 -1
  35. package/templates/docker/src/api/server.ts +27 -0
  36. package/templates/docker/src/app/api/[...route]/route.ts +2 -2
  37. package/templates/docker/src/app/globals.css +13 -12
  38. package/templates/docker/src/app/layout.tsx +9 -2
  39. package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
  40. package/templates/docker/src/components/ui/badge.tsx +48 -0
  41. package/templates/docker/src/components/ui/button.tsx +64 -0
  42. package/templates/docker/src/components/ui/card.tsx +92 -0
  43. package/templates/docker/src/components/ui/collapsible.tsx +33 -0
  44. package/templates/docker/src/components/ui/command.tsx +184 -0
  45. package/templates/docker/src/components/ui/dialog.tsx +158 -0
  46. package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
  47. package/templates/docker/src/components/ui/input.tsx +21 -0
  48. package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
  49. package/templates/docker/src/components/ui/select.tsx +190 -0
  50. package/templates/docker/src/components/ui/separator.tsx +28 -0
  51. package/templates/docker/src/components/ui/sheet.tsx +143 -0
  52. package/templates/docker/src/components/ui/sidebar.tsx +726 -0
  53. package/templates/docker/src/components/ui/skeleton.tsx +13 -0
  54. package/templates/docker/src/components/ui/table.tsx +116 -0
  55. package/templates/docker/src/components/ui/tabs.tsx +91 -0
  56. package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
  57. package/templates/docker/src/components/ui/toggle.tsx +47 -0
  58. package/templates/docker/src/components/ui/tooltip.tsx +57 -0
  59. package/templates/docker/src/hooks/use-mobile.ts +19 -0
  60. package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
  61. package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
  62. package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
  63. package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
  64. package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
  65. package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
  66. package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
  67. package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
  68. package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
  69. package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
  70. package/templates/docker/src/lib/agent-query.ts +5 -23
  71. package/templates/docker/src/lib/agent.ts +32 -112
  72. package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
  73. package/templates/docker/src/lib/auth/middleware.ts +30 -4
  74. package/templates/docker/src/lib/auth/migrate.ts +97 -0
  75. package/templates/docker/src/lib/auth/server.ts +12 -1
  76. package/templates/docker/src/lib/config.ts +38 -40
  77. package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
  78. package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
  79. package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
  80. package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
  81. package/templates/docker/src/lib/db/connection.ts +87 -265
  82. package/templates/docker/src/lib/db/internal.ts +6 -1
  83. package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
  84. package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
  85. package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
  86. package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
  87. package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
  88. package/templates/docker/src/lib/plugins/index.ts +4 -1
  89. package/templates/docker/src/lib/plugins/migrate.ts +104 -1
  90. package/templates/docker/src/lib/plugins/registry.ts +14 -8
  91. package/templates/docker/src/lib/plugins/wiring.ts +113 -4
  92. package/templates/docker/src/lib/providers.ts +6 -1
  93. package/templates/docker/src/lib/security.ts +24 -0
  94. package/templates/docker/src/lib/semantic.ts +2 -0
  95. package/templates/docker/src/lib/sidecar-types.ts +12 -1
  96. package/templates/docker/src/lib/startup.ts +71 -101
  97. package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
  98. package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
  99. package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
  100. package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
  101. package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
  102. package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
  103. package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
  104. package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
  105. package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
  106. package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
  107. package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
  108. package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
  109. package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
  110. package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
  111. package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
  112. package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
  113. package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
  114. package/templates/docker/src/lib/tools/explore.ts +28 -32
  115. package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
  116. package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
  117. package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
  118. package/templates/docker/src/lib/tools/python.ts +367 -0
  119. package/templates/docker/src/lib/tools/registry.ts +49 -22
  120. package/templates/docker/src/lib/tools/sql.ts +88 -88
  121. package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
  122. package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
  123. package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
  124. package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
  125. package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
  126. package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
  127. package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
  128. package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
  129. package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
  130. package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
  131. package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
  132. package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
  133. package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
  134. package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
  135. package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
  136. package/templates/docker/src/ui/context.tsx +1 -1
  137. package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
  138. package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
  139. package/templates/docker/tsconfig.json +2 -2
  140. package/templates/nextjs-standalone/.env.example +1 -1
  141. package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +11 -11
  142. package/templates/nextjs-standalone/bin/atlas.ts +120 -56
  143. package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
  144. package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
  145. package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
  146. package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
  147. package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
  148. package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
  149. package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
  150. package/templates/nextjs-standalone/docs/deploy.md +4 -41
  151. package/templates/nextjs-standalone/package.json +11 -2
  152. package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
  153. package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
  154. package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
  155. package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
  156. package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
  157. package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
  158. package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
  159. package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
  160. package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
  161. package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
  162. package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
  163. package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
  164. package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
  165. package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
  166. package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
  167. package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
  168. package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
  169. package/templates/nextjs-standalone/src/api/server.ts +27 -0
  170. package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
  171. package/templates/nextjs-standalone/src/app/globals.css +13 -12
  172. package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
  173. package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
  174. package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
  175. package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
  176. package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
  177. package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
  178. package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
  179. package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
  180. package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
  181. package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
  182. package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
  183. package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
  184. package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
  185. package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
  186. package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
  187. package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
  188. package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
  189. package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
  190. package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
  191. package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
  192. package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
  193. package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
  194. package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
  195. package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
  196. package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
  197. package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
  198. package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
  199. package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
  200. package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
  201. package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
  202. package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
  203. package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
  204. package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
  205. package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
  206. package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
  207. package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
  208. package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
  209. package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
  210. package/templates/nextjs-standalone/src/lib/config.ts +38 -40
  211. package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
  212. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
  213. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
  214. package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
  215. package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
  216. package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
  217. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
  218. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
  219. package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
  220. package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
  221. package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
  222. package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
  223. package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +104 -1
  224. package/templates/nextjs-standalone/src/lib/plugins/registry.ts +14 -8
  225. package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
  226. package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
  227. package/templates/nextjs-standalone/src/lib/security.ts +24 -0
  228. package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
  229. package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
  230. package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
  231. package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
  232. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
  233. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
  234. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
  235. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
  236. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
  237. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
  238. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
  239. package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
  240. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
  241. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
  242. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
  243. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
  244. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
  245. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
  246. package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
  247. package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
  248. package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
  249. package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
  250. package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
  251. package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
  252. package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
  253. package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
  254. package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
  255. package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
  256. package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
  257. package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
  258. package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
  259. package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
  260. package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
  261. package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
  262. package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
  263. package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
  264. package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
  265. package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
  266. package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
  267. package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
  268. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
  269. package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
  270. package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
  271. package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
  272. package/templates/nextjs-standalone/tsconfig.json +0 -1
  273. package/templates/nextjs-standalone/vercel.json +4 -1
  274. package/templates/docker/render.yaml +0 -34
  275. package/templates/docker/semantic/entities/.gitkeep +0 -0
  276. package/templates/docker/semantic/metrics/.gitkeep +0 -0
  277. package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
  278. package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
  279. package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
  280. package/templates/docker/src/lib/db/duckdb.ts +0 -122
  281. package/templates/docker/src/lib/db/salesforce.ts +0 -342
  282. package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
  283. package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
  284. package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
  285. package/templates/docker/src/lib/tools/salesforce.ts +0 -138
  286. package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
  287. package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
  288. package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
  289. package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
  290. package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
  291. package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
  292. package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
  293. package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
  294. package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
  295. package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
  296. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
  297. package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
  298. package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
@@ -28,6 +28,8 @@ interface DatasourceShape {
28
28
  create(): Promise<{ query(sql: string, timeoutMs?: number): Promise<unknown>; close(): Promise<void> }> | { query(sql: string, timeoutMs?: number): Promise<unknown>; close(): Promise<void> };
29
29
  dbType: string;
30
30
  validate?(query: string): { valid: boolean; reason?: string };
31
+ parserDialect?: string;
32
+ forbiddenPatterns?: RegExp[];
31
33
  };
32
34
  entities?: unknown[] | (() => Promise<unknown[]> | unknown[]);
33
35
  dialect?: string;
@@ -51,7 +53,7 @@ interface InteractionShape {
51
53
 
52
54
  function hasContextProvider(p: PluginLike): p is PluginLike & ContextShape {
53
55
  return (
54
- p.type === "context" &&
56
+ p.types.includes("context") &&
55
57
  typeof (p as Record<string, unknown>).contextProvider === "object" &&
56
58
  (p as Record<string, unknown>).contextProvider !== null &&
57
59
  typeof ((p as Record<string, unknown>).contextProvider as Record<string, unknown>)?.load === "function"
@@ -60,18 +62,18 @@ function hasContextProvider(p: PluginLike): p is PluginLike & ContextShape {
60
62
 
61
63
  function hasDatasource(p: PluginLike): p is PluginLike & DatasourceShape {
62
64
  return (
63
- p.type === "datasource" &&
65
+ p.types.includes("datasource") &&
64
66
  typeof (p as Record<string, unknown>).connection === "object" &&
65
67
  (p as Record<string, unknown>).connection !== null
66
68
  );
67
69
  }
68
70
 
69
71
  function hasActions(p: PluginLike): p is PluginLike & ActionShape {
70
- return p.type === "action" && Array.isArray((p as Record<string, unknown>).actions);
72
+ return p.types.includes("action") && Array.isArray((p as Record<string, unknown>).actions);
71
73
  }
72
74
 
73
75
  function hasRoutes(p: PluginLike): p is PluginLike & InteractionShape {
74
- return p.type === "interaction" && typeof (p as Record<string, unknown>).routes === "function";
76
+ return p.types.includes("interaction") && typeof (p as Record<string, unknown>).routes === "function";
75
77
  }
76
78
 
77
79
  // ---------------------------------------------------------------------------
@@ -106,12 +108,16 @@ export async function wireDatasourcePlugins(
106
108
  }
107
109
  try {
108
110
  const conn = await plugin.connection.create();
111
+ const meta = (plugin.connection.parserDialect || plugin.connection.forbiddenPatterns)
112
+ ? { parserDialect: plugin.connection.parserDialect, forbiddenPatterns: plugin.connection.forbiddenPatterns }
113
+ : undefined;
109
114
  await connRegistry.registerDirect(
110
115
  plugin.id,
111
116
  conn as Parameters<ConnectionRegistry["registerDirect"]>[1],
112
117
  plugin.connection.dbType as Parameters<ConnectionRegistry["registerDirect"]>[2],
113
118
  plugin.name ?? plugin.id,
114
119
  plugin.connection.validate,
120
+ meta,
115
121
  );
116
122
  wired.push(plugin.id);
117
123
  log.info({ pluginId: plugin.id, dbType: plugin.connection.dbType }, "Datasource plugin wired");
@@ -254,6 +260,109 @@ export async function wireInteractionPlugins(
254
260
  return { wired, failed };
255
261
  }
256
262
 
263
+ // ---------------------------------------------------------------------------
264
+ // Sandbox plugins
265
+ // ---------------------------------------------------------------------------
266
+
267
+ /**
268
+ * Duplicated from @useatlas/plugin-sdk/types to avoid runtime SDK dependency.
269
+ * Keep in sync — explore-sdk-compat.test.ts verifies structural equivalence.
270
+ */
271
+ const SANDBOX_DEFAULT_PRIORITY = 60;
272
+
273
+ /**
274
+ * Minimal interface for a sandbox execution backend.
275
+ * Structurally identical to ExploreBackend in explore.ts — duplicated here
276
+ * to avoid a circular dependency (explore.ts imports from wiring.ts).
277
+ * Changes to either interface should be mirrored.
278
+ */
279
+ export interface SandboxExecBackend {
280
+ exec(command: string): Promise<{ stdout: string; stderr: string; exitCode: number }>;
281
+ close?(): Promise<void>;
282
+ }
283
+
284
+ interface SandboxShape {
285
+ sandbox: {
286
+ create(root: string): Promise<SandboxExecBackend> | SandboxExecBackend;
287
+ priority?: number;
288
+ };
289
+ }
290
+
291
+ function hasSandbox(p: PluginLike): p is PluginLike & SandboxShape {
292
+ return (
293
+ p.types.includes("sandbox") &&
294
+ typeof (p as Record<string, unknown>).sandbox === "object" &&
295
+ (p as Record<string, unknown>).sandbox !== null &&
296
+ typeof ((p as Record<string, unknown>).sandbox as Record<string, unknown>)?.create === "function"
297
+ );
298
+ }
299
+
300
+ /**
301
+ * Discover sandbox plugins, sort by priority (highest first), and try to
302
+ * create a backend from each until one succeeds.
303
+ *
304
+ * Unlike other wire functions, sandbox plugins are not registered into a
305
+ * global registry — the caller receives a single backend instance directly.
306
+ *
307
+ * NOTE: In practice, called lazily from getExploreBackend() on the first
308
+ * explore command (not at startup), because the explore backend is cached
309
+ * as a singleton and depends on runtime environment detection.
310
+ */
311
+ export async function wireSandboxPlugins(
312
+ pluginRegistry: PluginRegistry,
313
+ semanticRoot: string,
314
+ ): Promise<{
315
+ backend: SandboxExecBackend | null;
316
+ pluginId: string | null;
317
+ failed: Array<{ pluginId: string; error: string }>;
318
+ }> {
319
+ const sandboxPlugins = pluginRegistry.getByType("sandbox");
320
+ const failed: Array<{ pluginId: string; error: string }> = [];
321
+
322
+ if (sandboxPlugins.length === 0) {
323
+ return { backend: null, pluginId: null, failed };
324
+ }
325
+
326
+ // Filter to valid sandbox plugins, sort by priority descending
327
+ const valid = sandboxPlugins.filter(hasSandbox);
328
+ if (valid.length === 0) {
329
+ for (const sp of sandboxPlugins) {
330
+ log.warn({ pluginId: sp.id }, "Sandbox plugin missing sandbox.create() — skipped");
331
+ }
332
+ return { backend: null, pluginId: null, failed };
333
+ }
334
+
335
+ const sorted = [...valid].sort((a, b) => {
336
+ const pa = a.sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
337
+ const pb = b.sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
338
+ return pb - pa;
339
+ });
340
+
341
+ for (const sp of sorted) {
342
+ try {
343
+ const backend = await sp.sandbox.create(semanticRoot);
344
+ if (!backend || typeof backend.exec !== "function") {
345
+ const msg = "create() returned invalid backend (missing exec method)";
346
+ failed.push({ pluginId: sp.id, error: msg });
347
+ log.error({ pluginId: sp.id }, msg);
348
+ continue;
349
+ }
350
+ log.info({ pluginId: sp.id }, "Using sandbox plugin for explore backend");
351
+ return { backend, pluginId: sp.id, failed };
352
+ } catch (err) {
353
+ const msg = err instanceof Error ? err.message : String(err);
354
+ failed.push({ pluginId: sp.id, error: msg });
355
+ log.error(
356
+ { pluginId: sp.id, err: err instanceof Error ? err : new Error(String(err)) },
357
+ "Sandbox plugin create() failed, trying next",
358
+ );
359
+ }
360
+ }
361
+
362
+ log.error({ count: sorted.length }, "All sandbox plugins failed to create a backend");
363
+ return { backend: null, pluginId: null, failed };
364
+ }
365
+
257
366
  /**
258
367
  * For each healthy context plugin, call `contextProvider.load()` and collect
259
368
  * the returned text fragments. Fragments are injected into the agent system
@@ -34,6 +34,11 @@ const PROVIDER_DEFAULTS: Record<ConfigProvider, string> = {
34
34
  gateway: "anthropic/claude-opus-4.6",
35
35
  };
36
36
 
37
+ /** Returns the default provider string based on runtime environment. */
38
+ export function getDefaultProvider(): ConfigProvider {
39
+ return process.env.VERCEL ? "gateway" : "anthropic";
40
+ }
41
+
37
42
  function isBedrockAnthropicModel(modelId: string): boolean {
38
43
  return modelId.includes("anthropic") || modelId.includes("claude");
39
44
  }
@@ -43,7 +48,7 @@ function isBedrockAnthropicModel(modelId: string): boolean {
43
48
  * Returns the validated config provider string and the resolved model ID.
44
49
  */
45
50
  function resolveProvider(): { provider: ConfigProvider; modelId: string } {
46
- const raw = process.env.ATLAS_PROVIDER ?? "anthropic";
51
+ const raw = process.env.ATLAS_PROVIDER ?? getDefaultProvider();
47
52
  if (!VALID_PROVIDERS.has(raw as ConfigProvider)) {
48
53
  throw new Error(
49
54
  `Unknown provider "${raw}". Supported: ${[...VALID_PROVIDERS].join(", ")}`
@@ -9,3 +9,27 @@
9
9
 
10
10
  export const SENSITIVE_PATTERNS =
11
11
  /password|secret|credential|connection.?string|pg_hba\.conf|SSL|certificate|Access denied for user|ER_ACCESS_DENIED_ERROR|ER_DBACCESS_DENIED_ERROR|ER_BAD_HOST_ERROR|ER_HOST_NOT_PRIVILEGED|ER_SPECIFIC_ACCESS_DENIED_ERROR|PROTOCOL_CONNECTION_LOST|Can't connect to MySQL server|Authentication failed|DB::Exception.*Authentication|UNKNOWN_USER|WRONG_PASSWORD|REQUIRED_PASSWORD|IP_ADDRESS_NOT_ALLOWED|ALL_CONNECTION_TRIES_FAILED|CLIENT_HAS_CONNECTED_TO_WRONG_PORT|AUTHENTICATION_FAILED|INVALID_SESSION_ID|LOGIN_MUST_USE_SECURITY_TOKEN|INVALID_LOGIN|INVALID_CLIENT_ID/i;
12
+
13
+ /**
14
+ * Mask credentials in a database connection URL.
15
+ * Returns "<invalid-url>" for unparseable URLs to avoid leaking raw strings.
16
+ */
17
+ const SENSITIVE_PARAMS = /^(password|secret|token|key|credential|auth)$/i;
18
+
19
+ export function maskConnectionUrl(url: string): string {
20
+ try {
21
+ const parsed = new URL(url);
22
+ if (parsed.username || parsed.password) {
23
+ parsed.username = "***";
24
+ parsed.password = "";
25
+ }
26
+ for (const key of [...parsed.searchParams.keys()]) {
27
+ if (SENSITIVE_PARAMS.test(key)) {
28
+ parsed.searchParams.set(key, "***");
29
+ }
30
+ }
31
+ return parsed.toString();
32
+ } catch {
33
+ return "<invalid-url>";
34
+ }
35
+ }
@@ -86,6 +86,8 @@ function loadEntitiesFromDir(
86
86
  for (const file of files) {
87
87
  try {
88
88
  const content = fs.readFileSync(path.join(dir, file), "utf-8");
89
+ // js-yaml v4+ yaml.load() uses DEFAULT_SCHEMA (JSON + core YAML types) which is
90
+ // safe — it does not instantiate arbitrary JS objects (unlike v3's yaml.load).
89
91
  const raw = yaml.load(content);
90
92
  const parsed = EntityShape.safeParse(raw);
91
93
  if (!parsed.success) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Types for the sidecar HTTP contract.
3
3
  * Used by both the sidecar server (packages/sandbox-sidecar) and
4
- * the sidecar client (packages/api/src/lib/tools/explore-sidecar.ts).
4
+ * the sidecar clients (explore-sidecar.ts, python-sidecar.ts).
5
5
  */
6
6
 
7
7
  export interface SidecarExecRequest {
@@ -14,3 +14,14 @@ export interface SidecarExecResponse {
14
14
  stderr: string;
15
15
  exitCode: number;
16
16
  }
17
+
18
+ // --- Python execution ---
19
+
20
+ export interface SidecarPythonRequest {
21
+ code: string;
22
+ data?: { columns: string[]; rows: unknown[][] };
23
+ timeout?: number;
24
+ }
25
+
26
+ /** Wire-format alias — canonical type lives in python.ts. */
27
+ export type { PythonResult as SidecarPythonResponse } from "@atlas/api/lib/tools/python";
@@ -7,7 +7,9 @@
7
7
 
8
8
  import * as fs from "fs";
9
9
  import * as path from "path";
10
- import { detectDBType } from "./db/connection";
10
+ import { detectDBType, resolveDatasourceUrl } from "./db/connection";
11
+ import { maskConnectionUrl } from "./security";
12
+ import { getDefaultProvider } from "./providers";
11
13
  import { detectAuthMode, getAuthModeSource } from "./auth/detect";
12
14
  import { createLogger } from "./logger";
13
15
 
@@ -18,7 +20,8 @@ export type DiagnosticCode =
18
20
  | "MISSING_SEMANTIC_LAYER" | "INVALID_SCHEMA" | "INTERNAL_DB_UNREACHABLE"
19
21
  | "WEAK_AUTH_SECRET" | "INVALID_JWKS_URL" | "MISSING_AUTH_ISSUER"
20
22
  | "MISSING_AUTH_PREREQ"
21
- | "ACTIONS_REQUIRE_AUTH" | "ACTIONS_MISSING_CREDENTIALS";
23
+ | "ACTIONS_REQUIRE_AUTH" | "ACTIONS_MISSING_CREDENTIALS"
24
+ | "INVALID_CONFIG";
22
25
 
23
26
  export interface DiagnosticError {
24
27
  code: DiagnosticCode;
@@ -33,6 +36,12 @@ const PROVIDER_KEY_MAP: Record<string, string> = {
33
36
  gateway: "AI_GATEWAY_API_KEY",
34
37
  };
35
38
 
39
+ const PROVIDER_SIGNUP_URL: Record<string, string> = {
40
+ anthropic: "https://console.anthropic.com/settings/keys",
41
+ openai: "https://platform.openai.com/api-keys",
42
+ gateway: "https://vercel.com/~/ai/api-keys",
43
+ };
44
+
36
45
  let _cached: DiagnosticError[] | null = null;
37
46
  let _cachedAt = 0;
38
47
  const _startupWarnings: string[] = [];
@@ -67,14 +76,23 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
67
76
 
68
77
  const errors: DiagnosticError[] = [];
69
78
 
70
- // 1. ATLAS_DATASOURCE_URLerror if DATABASE_URL looks like a migration leftover
71
- if (!process.env.ATLAS_DATASOURCE_URL) {
72
- if (process.env.DATABASE_URL) {
79
+ // 1. Analytics datasource resolve from ATLAS_DATASOURCE_URL or Neon fallback
80
+ const resolvedDatasourceUrl = resolveDatasourceUrl();
81
+ if (!resolvedDatasourceUrl) {
82
+ if (process.env.ATLAS_DEMO_DATA === "true") {
83
+ const msg =
84
+ "ATLAS_DEMO_DATA=true but neither DATABASE_URL_UNPOOLED nor DATABASE_URL is set. " +
85
+ "The Neon integration may not have provisioned a database. " +
86
+ "Check your Vercel project's storage integrations.";
87
+ log.error(msg);
88
+ errors.push({ code: "MISSING_DATASOURCE_URL", message: msg });
89
+ } else if (process.env.DATABASE_URL) {
73
90
  const msg =
74
91
  "DATABASE_URL is set but ATLAS_DATASOURCE_URL is not. " +
75
92
  "As of v0.5, the analytics datasource uses ATLAS_DATASOURCE_URL. " +
76
93
  "DATABASE_URL is now reserved for Atlas's internal Postgres. " +
77
- "Rename your analytics connection to ATLAS_DATASOURCE_URL.";
94
+ "Rename your analytics connection to ATLAS_DATASOURCE_URL, " +
95
+ "or set ATLAS_DEMO_DATA=true to use the same database for demo data.";
78
96
  log.error(msg);
79
97
  errors.push({ code: "MISSING_DATASOURCE_URL", message: msg });
80
98
  } else {
@@ -87,19 +105,23 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
87
105
  }
88
106
  log.warn(msg);
89
107
  }
108
+ } else if (!process.env.ATLAS_DATASOURCE_URL && process.env.ATLAS_DEMO_DATA === "true") {
109
+ const source = process.env.DATABASE_URL_UNPOOLED ? "DATABASE_URL_UNPOOLED" : "DATABASE_URL";
110
+ log.info("Demo mode: using %s as analytics datasource", source);
90
111
  }
91
112
 
92
113
  // 2. API key for configured provider
93
- const provider = process.env.ATLAS_PROVIDER ?? "anthropic";
114
+ const provider = process.env.ATLAS_PROVIDER ?? getDefaultProvider();
94
115
  const requiredKey = PROVIDER_KEY_MAP[provider];
95
116
 
96
117
  if (requiredKey === undefined) {
97
118
  // Unknown provider — providers.ts will throw a descriptive error at model init,
98
119
  // so we don't duplicate that check here.
99
120
  } else if (requiredKey && !process.env[requiredKey]) {
100
- let message = `${requiredKey} is not set. Atlas needs an API key for the ${provider} provider.`;
101
- if (provider === "gateway") {
102
- message += " Create one at https://vercel.com/~/ai/api-keys";
121
+ let message = `${requiredKey} is not set. Atlas needs an API key for the "${provider}" provider. Set it in your .env file.`;
122
+ const signupUrl = PROVIDER_SIGNUP_URL[provider];
123
+ if (signupUrl) {
124
+ message += ` Get one at ${signupUrl}`;
103
125
  }
104
126
  errors.push({ code: "MISSING_API_KEY", message });
105
127
  }
@@ -129,11 +151,11 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
129
151
  });
130
152
  }
131
153
 
132
- // 4. Datasource connectivity (only if ATLAS_DATASOURCE_URL is set)
133
- if (process.env.ATLAS_DATASOURCE_URL) {
154
+ // 4. Datasource connectivity (only if a datasource URL is resolved)
155
+ if (resolvedDatasourceUrl) {
134
156
  let dbType: ReturnType<typeof detectDBType> | null = null;
135
157
  try {
136
- dbType = detectDBType();
158
+ dbType = detectDBType(resolvedDatasourceUrl);
137
159
  } catch (err) {
138
160
  const detail = err instanceof Error ? err.message : String(err);
139
161
  log.error({ err: detail }, "Unsupported datasource URL");
@@ -142,7 +164,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
142
164
 
143
165
  if (dbType === "mysql") {
144
166
  // MySQL: URL validation + connection test
145
- if (!isValidUrl(process.env.ATLAS_DATASOURCE_URL)) {
167
+ if (!isValidUrl(resolvedDatasourceUrl)) {
146
168
  errors.push({
147
169
  code: "DB_UNREACHABLE",
148
170
  message: "ATLAS_DATASOURCE_URL appears malformed. Expected format: mysql://user:pass@host:3306/dbname",
@@ -153,7 +175,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
153
175
  let pool;
154
176
  try {
155
177
  pool = mysql.createPool({
156
- uri: process.env.ATLAS_DATASOURCE_URL,
178
+ uri: resolvedDatasourceUrl,
157
179
  connectionLimit: 1,
158
180
  connectTimeout: 5000,
159
181
  });
@@ -163,7 +185,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
163
185
  const detail = err instanceof Error ? err.message : "";
164
186
  log.error({ err: detail }, "MySQL connection check failed");
165
187
 
166
- let message = "Cannot connect to the database. Check that the server is running and the connection string is correct.";
188
+ let message = `Cannot connect to ${maskConnectionUrl(resolvedDatasourceUrl)}. Check the connection string and ensure the database is running.`;
167
189
 
168
190
  if (/ECONNREFUSED/i.test(detail)) {
169
191
  message += " The connection was refused — is the MySQL server running?";
@@ -184,47 +206,6 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
184
206
  }
185
207
  }
186
208
  }
187
- } else if (dbType === "clickhouse") {
188
- // ClickHouse: connectivity test via SELECT 1
189
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
- let createClient: any = null;
191
- try {
192
- // eslint-disable-next-line @typescript-eslint/no-require-imports
193
- ({ createClient } = require("@clickhouse/client"));
194
- } catch {
195
- errors.push({
196
- code: "DB_UNREACHABLE",
197
- message: "ClickHouse support requires the @clickhouse/client package. Install it with: bun add @clickhouse/client",
198
- });
199
- }
200
-
201
- if (createClient) {
202
- const { rewriteClickHouseUrl } = await import("./db/connection");
203
- const httpUrl = rewriteClickHouseUrl(process.env.ATLAS_DATASOURCE_URL!);
204
- const client = createClient({ url: httpUrl });
205
- try {
206
- await client.query({ query: "SELECT 1", format: "JSON" });
207
- } catch (err) {
208
- const detail = err instanceof Error ? err.message : "";
209
- log.error({ err: detail }, "ClickHouse connection check failed");
210
-
211
- let message = "Cannot connect to ClickHouse. Check that the server is running and the connection string is correct.";
212
-
213
- if (/ECONNREFUSED/i.test(detail)) {
214
- message += " The connection was refused — is the ClickHouse server running?";
215
- } else if (/Authentication/i.test(detail) || /AUTHENTICATION_FAILED/i.test(detail)) {
216
- message += " Authentication failed — check your username and password.";
217
- } else if (/timeout/i.test(detail)) {
218
- message += " The connection timed out — check network/firewall settings.";
219
- }
220
-
221
- errors.push({ code: "DB_UNREACHABLE", message });
222
- } finally {
223
- await client.close().catch((err: unknown) => {
224
- log.warn({ err: err instanceof Error ? err.message : String(err) }, "ClickHouse client cleanup warning");
225
- });
226
- }
227
- }
228
209
  } else if (dbType === "postgres") {
229
210
  // PostgreSQL: existing URL validation + connection test + schema validation
230
211
  const atlasSchema = process.env.ATLAS_SCHEMA;
@@ -238,7 +219,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
238
219
  });
239
220
  }
240
221
 
241
- if (!isValidUrl(process.env.ATLAS_DATASOURCE_URL)) {
222
+ if (!isValidUrl(resolvedDatasourceUrl)) {
242
223
  errors.push({
243
224
  code: "DB_UNREACHABLE",
244
225
  message: "ATLAS_DATASOURCE_URL appears malformed. Expected format: postgresql://user:pass@host:5432/dbname",
@@ -247,7 +228,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
247
228
  // eslint-disable-next-line @typescript-eslint/no-require-imports
248
229
  const { Pool } = require("pg");
249
230
  const pool = new Pool({
250
- connectionString: process.env.ATLAS_DATASOURCE_URL,
231
+ connectionString: resolvedDatasourceUrl,
251
232
  max: 1,
252
233
  connectionTimeoutMillis: 5000,
253
234
  });
@@ -281,7 +262,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
281
262
  const detail = err instanceof Error ? err.message : "";
282
263
  log.error({ err: detail }, "DB connection check failed");
283
264
 
284
- let message = "Cannot connect to the database. Check that the server is running and the connection string is correct.";
265
+ let message = `Cannot connect to ${maskConnectionUrl(resolvedDatasourceUrl)}. Check the connection string and ensure the database is running.`;
285
266
 
286
267
  if (/ECONNREFUSED/i.test(detail)) {
287
268
  message += " The connection was refused — is the database server running?";
@@ -298,46 +279,14 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
298
279
  });
299
280
  }
300
281
  }
301
- } else if (dbType === "salesforce") {
302
- // Salesforce: test login + listObjects
303
- try {
304
- const { parseSalesforceURL, createSalesforceDataSource } = await import("./db/salesforce");
305
- const config = parseSalesforceURL(process.env.ATLAS_DATASOURCE_URL!);
306
- const source = createSalesforceDataSource(config);
307
- try {
308
- await source.listObjects();
309
- } finally {
310
- await source.close().catch((err: unknown) => {
311
- log.warn({ err: err instanceof Error ? err.message : String(err) }, "Salesforce cleanup warning");
312
- });
313
- }
314
- } catch (err) {
315
- const detail = err instanceof Error ? err.message : "";
316
- log.error({ err: detail }, "Salesforce connection check failed");
317
-
318
- let message = "Cannot connect to Salesforce. Check that your credentials and connection string are correct.";
319
-
320
- if (/LOGIN_MUST_USE_SECURITY_TOKEN/i.test(detail)) {
321
- message += " A security token is required — add ?token=YOUR_TOKEN to the connection URL.";
322
- } else if (/INVALID_LOGIN/i.test(detail)) {
323
- message += " Authentication failed — check your username and password.";
324
- } else if (/timeout/i.test(detail)) {
325
- message += " The connection timed out — check network/firewall settings.";
326
- }
327
-
328
- errors.push({ code: "DB_UNREACHABLE", message });
329
- }
330
282
  }
331
-
332
- // Snowflake: warn that SQL validation is the sole read-only enforcement layer
333
- if (dbType === "snowflake") {
334
- const msg =
335
- "Snowflake connections rely solely on SQL validation for read-only enforcement (no session-level read-only mode). " +
336
- "Configure the Snowflake role with SELECT-only privileges for defense-in-depth.";
337
- if (!_startupWarnings.includes(msg)) {
338
- _startupWarnings.push(msg);
339
- }
340
- log.warn(msg);
283
+ // Non-core database types are validated by their respective datasource plugins.
284
+ if (dbType && dbType !== "postgres" && dbType !== "mysql") {
285
+ log.info(
286
+ { dbType },
287
+ "Non-core datasource type '%s' connectivity validation deferred to plugin initialize()",
288
+ dbType,
289
+ );
341
290
  }
342
291
  }
343
292
 
@@ -363,7 +312,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
363
312
  const detail = err instanceof Error ? err.message : "";
364
313
  log.error({ err: detail }, "Internal DB connection check failed");
365
314
 
366
- let message = "Cannot connect to the internal database (DATABASE_URL). Check that the server is running and the connection string is correct.";
315
+ let message = `Cannot connect to the internal database at ${maskConnectionUrl(process.env.DATABASE_URL!)}. Check the connection string and ensure the database is running.`;
367
316
  if (/ECONNREFUSED/i.test(detail)) {
368
317
  message += " The connection was refused — is the database server running?";
369
318
  } else if (/timeout/i.test(detail)) {
@@ -394,6 +343,18 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
394
343
  errors.push({ code: "INTERNAL_DB_UNREACHABLE", message: migrationErr });
395
344
  }
396
345
 
346
+ // 5.5. Config file validation (atlas.config.ts)
347
+ try {
348
+ const configMod = await import("@atlas/api/lib/config");
349
+ if (typeof configMod.loadConfig === "function" && !configMod.getConfig()) {
350
+ await configMod.loadConfig();
351
+ }
352
+ } catch (err) {
353
+ const detail = err instanceof Error ? err.message : String(err);
354
+ log.error({ err: detail }, "Config validation failed");
355
+ errors.push({ code: "INVALID_CONFIG", message: detail });
356
+ }
357
+
397
358
  // 6. Auth mode diagnostics
398
359
  const authMode = detectAuthMode();
399
360
  const authSource = getAuthModeSource();
@@ -441,7 +402,8 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
441
402
  errors.push({
442
403
  code: "WEAK_AUTH_SECRET",
443
404
  message:
444
- "BETTER_AUTH_SECRET is shorter than 32 characters. Use a cryptographically random string of at least 32 characters.",
405
+ `BETTER_AUTH_SECRET must be at least 32 characters (currently ${secret.length}). ` +
406
+ "Generate one with: openssl rand -base64 32",
445
407
  });
446
408
  }
447
409
  if (!process.env.BETTER_AUTH_URL) {
@@ -490,6 +452,14 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
490
452
  "ATLAS_AUTH_ISSUER is required for BYOT auth mode. Set it to your identity provider's issuer URL (e.g. https://your-idp.com/).",
491
453
  });
492
454
  }
455
+
456
+ if (process.env.ATLAS_AUTH_AUDIENCE === "") {
457
+ const msg =
458
+ "ATLAS_AUTH_AUDIENCE is set to an empty string — audience validation will be skipped. " +
459
+ "Remove the variable entirely if audience checking is not needed, or set it to a valid audience value.";
460
+ if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
461
+ log.warn(msg);
462
+ }
493
463
  }
494
464
 
495
465
  // Warn about orphaned auth env vars that suggest misconfiguration
@@ -72,6 +72,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
72
72
  getTargetHost: () => "localhost",
73
73
  list: () => ["default", "salesforce-plugin"],
74
74
  getValidator: (id: string) => validatorMap.get(id),
75
+ getParserDialect: () => undefined,
76
+ getForbiddenPatterns: () => [],
75
77
  },
76
78
  detectDBType: () => "postgres",
77
79
  }));