@useatlas/create 0.0.2 → 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 (296) 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 +9 -9
  7. package/templates/docker/bin/atlas.ts +108 -44
  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 +2 -39
  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 +37 -39
  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 +103 -0
  90. package/templates/docker/src/lib/plugins/registry.ts +12 -6
  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-sidecar.test.ts +5 -3
  101. package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
  102. package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
  103. package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
  104. package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
  105. package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
  106. package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
  107. package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
  108. package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
  109. package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
  110. package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
  111. package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
  112. package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
  113. package/templates/docker/src/lib/tools/explore.ts +28 -32
  114. package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
  115. package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
  116. package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
  117. package/templates/docker/src/lib/tools/python.ts +367 -0
  118. package/templates/docker/src/lib/tools/registry.ts +49 -22
  119. package/templates/docker/src/lib/tools/sql.ts +88 -88
  120. package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
  121. package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
  122. package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
  123. package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
  124. package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
  125. package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
  126. package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
  127. package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
  128. package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
  129. package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
  130. package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
  131. package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
  132. package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
  133. package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
  134. package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
  135. package/templates/docker/src/ui/context.tsx +1 -1
  136. package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
  137. package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
  138. package/templates/docker/tsconfig.json +2 -2
  139. package/templates/nextjs-standalone/.env.example +1 -1
  140. package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +9 -9
  141. package/templates/nextjs-standalone/bin/atlas.ts +108 -44
  142. package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
  143. package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
  144. package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
  145. package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
  146. package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
  147. package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
  148. package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
  149. package/templates/nextjs-standalone/docs/deploy.md +2 -39
  150. package/templates/nextjs-standalone/package.json +11 -2
  151. package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
  152. package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
  153. package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
  154. package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
  155. package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
  156. package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
  157. package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
  158. package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
  159. package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
  160. package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
  161. package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
  162. package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
  163. package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
  164. package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
  165. package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
  166. package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
  167. package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
  168. package/templates/nextjs-standalone/src/api/server.ts +27 -0
  169. package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
  170. package/templates/nextjs-standalone/src/app/globals.css +13 -12
  171. package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
  172. package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
  173. package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
  174. package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
  175. package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
  176. package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
  177. package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
  178. package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
  179. package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
  180. package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
  181. package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
  182. package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
  183. package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
  184. package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
  185. package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
  186. package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
  187. package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
  188. package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
  189. package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
  190. package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
  191. package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
  192. package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
  193. package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
  194. package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
  195. package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
  196. package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
  197. package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
  198. package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
  199. package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
  200. package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
  201. package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
  202. package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
  203. package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
  204. package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
  205. package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
  206. package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
  207. package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
  208. package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
  209. package/templates/nextjs-standalone/src/lib/config.ts +37 -39
  210. package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
  211. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
  212. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
  213. package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
  214. package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
  215. package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
  216. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
  217. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
  218. package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
  219. package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
  220. package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
  221. package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
  222. package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +103 -0
  223. package/templates/nextjs-standalone/src/lib/plugins/registry.ts +12 -6
  224. package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
  225. package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
  226. package/templates/nextjs-standalone/src/lib/security.ts +24 -0
  227. package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
  228. package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
  229. package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
  230. package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
  231. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
  232. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
  233. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
  234. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
  235. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
  236. package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
  237. package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
  238. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
  239. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
  240. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
  241. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
  242. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
  243. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
  244. package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
  245. package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
  246. package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
  247. package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
  248. package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
  249. package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
  250. package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
  251. package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
  252. package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
  253. package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
  254. package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
  255. package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
  256. package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
  257. package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
  258. package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
  259. package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
  260. package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
  261. package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
  262. package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
  263. package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
  264. package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
  265. package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
  266. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
  267. package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
  268. package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
  269. package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
  270. package/templates/nextjs-standalone/tsconfig.json +0 -1
  271. package/templates/nextjs-standalone/vercel.json +4 -1
  272. package/templates/docker/render.yaml +0 -34
  273. package/templates/docker/semantic/entities/.gitkeep +0 -0
  274. package/templates/docker/semantic/metrics/.gitkeep +0 -0
  275. package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
  276. package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
  277. package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
  278. package/templates/docker/src/lib/db/duckdb.ts +0 -122
  279. package/templates/docker/src/lib/db/salesforce.ts +0 -342
  280. package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
  281. package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
  282. package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
  283. package/templates/docker/src/lib/tools/salesforce.ts +0 -138
  284. package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
  285. package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
  286. package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
  287. package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
  288. package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
  289. package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
  290. package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
  291. package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
  292. package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
  293. package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
  294. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
  295. package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
  296. package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
@@ -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
  }));
@@ -1,10 +1,33 @@
1
- import { describe, expect, it, beforeEach, afterEach, spyOn } from "bun:test";
1
+ import { describe, expect, it, beforeEach, afterEach, spyOn, mock } from "bun:test";
2
2
  import * as fs from "fs";
3
3
 
4
4
  // ---------------------------------------------------------------------------
5
5
  // Mocks
6
6
  // ---------------------------------------------------------------------------
7
7
 
8
+ // Mock structured logger — must be before explore-nsjail import
9
+ let logWarnCalls: unknown[][] = [];
10
+ let logErrorCalls: unknown[][] = [];
11
+
12
+ const mockLogger = {
13
+ info: () => {},
14
+ warn: (...args: unknown[]) => { logWarnCalls.push(args); },
15
+ error: (...args: unknown[]) => { logErrorCalls.push(args); },
16
+ debug: () => {},
17
+ fatal: () => {},
18
+ trace: () => {},
19
+ child: () => mockLogger,
20
+ level: "info",
21
+ };
22
+
23
+ mock.module("@atlas/api/lib/logger", () => ({
24
+ createLogger: () => mockLogger,
25
+ getLogger: () => mockLogger,
26
+ withRequestContext: (_ctx: unknown, fn: () => unknown) => fn(),
27
+ getRequestContext: () => undefined,
28
+ redactPaths: [],
29
+ }));
30
+
8
31
  // Track Bun.spawn calls
9
32
  let spawnCalls: { args: unknown[]; options: unknown }[] = [];
10
33
  let spawnResult: {
@@ -50,7 +73,8 @@ const callbacks = {
50
73
  },
51
74
  };
52
75
 
53
- import { isNsjailAvailable, createNsjailBackend, testNsjailCapabilities } from "@atlas/api/lib/tools/explore-nsjail";
76
+ const { isNsjailAvailable, createNsjailBackend, testNsjailCapabilities } =
77
+ await import("@atlas/api/lib/tools/explore-nsjail");
54
78
 
55
79
  // ---------------------------------------------------------------------------
56
80
  // Helpers
@@ -145,6 +169,8 @@ describe("exec", () => {
145
169
  spawnCalls = [];
146
170
  invalidateCalled = false;
147
171
  markFailedCalled = false;
172
+ logWarnCalls = [];
173
+ logErrorCalls = [];
148
174
  setSpawnResult("", "", 0);
149
175
  });
150
176
 
@@ -311,8 +337,6 @@ describe("exec", () => {
311
337
  new Set(["/usr/local/bin/nsjail", SEMANTIC_ROOT]),
312
338
  );
313
339
 
314
- const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
315
-
316
340
  const backend = await createNsjailBackend(SEMANTIC_ROOT, callbacks);
317
341
  await backend.exec("ls");
318
342
 
@@ -320,7 +344,6 @@ describe("exec", () => {
320
344
  const tIndex = args.indexOf("-t");
321
345
  expect(args[tIndex + 1]).toBe("10"); // default
322
346
 
323
- warnSpy.mockRestore();
324
347
  spy.mockRestore();
325
348
  });
326
349
 
@@ -331,8 +354,6 @@ describe("exec", () => {
331
354
  new Set(["/usr/local/bin/nsjail", SEMANTIC_ROOT]),
332
355
  );
333
356
 
334
- const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
335
-
336
357
  const backend = await createNsjailBackend(SEMANTIC_ROOT, callbacks);
337
358
  await backend.exec("ls");
338
359
 
@@ -340,7 +361,6 @@ describe("exec", () => {
340
361
  const tIndex = args.indexOf("-t");
341
362
  expect(args[tIndex + 1]).toBe("10"); // default
342
363
 
343
- warnSpy.mockRestore();
344
364
  spy.mockRestore();
345
365
  });
346
366
 
@@ -351,8 +371,6 @@ describe("exec", () => {
351
371
  new Set(["/usr/local/bin/nsjail", SEMANTIC_ROOT]),
352
372
  );
353
373
 
354
- const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
355
-
356
374
  const backend = await createNsjailBackend(SEMANTIC_ROOT, callbacks);
357
375
  await backend.exec("ls");
358
376
 
@@ -360,7 +378,6 @@ describe("exec", () => {
360
378
  const memIndex = args.indexOf("--rlimit_as");
361
379
  expect(args[memIndex + 1]).toBe("256"); // default
362
380
 
363
- warnSpy.mockRestore();
364
381
  spy.mockRestore();
365
382
  });
366
383
 
@@ -371,7 +388,6 @@ describe("exec", () => {
371
388
  );
372
389
 
373
390
  setSpawnResult("", "nsjail setup failure", 109);
374
- const errorSpy = spyOn(console, "error").mockImplementation(() => {});
375
391
 
376
392
  const backend = await createNsjailBackend(SEMANTIC_ROOT, callbacks);
377
393
  const result = await backend.exec("ls");
@@ -379,7 +395,6 @@ describe("exec", () => {
379
395
  expect(result.exitCode).toBe(109);
380
396
  expect(markFailedCalled).toBe(true);
381
397
 
382
- errorSpy.mockRestore();
383
398
  spy.mockRestore();
384
399
  });
385
400
 
@@ -391,17 +406,16 @@ describe("exec", () => {
391
406
 
392
407
  // Exit code 137 = 128 + 9 (SIGKILL)
393
408
  setSpawnResult("", "", 137);
394
- const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
395
409
 
396
410
  const backend = await createNsjailBackend(SEMANTIC_ROOT, callbacks);
397
411
  const result = await backend.exec("sleep 999");
398
412
 
399
413
  expect(result.exitCode).toBe(137);
400
- expect(warnSpy).toHaveBeenCalled();
401
- const warnMsg = warnSpy.mock.calls[0]?.[0] as string;
402
- expect(warnMsg).toContain("signal 9");
414
+ // log.warn({ signal, command }, "nsjail child killed by signal")
415
+ expect(logWarnCalls.length).toBeGreaterThanOrEqual(1);
416
+ const warnObj = logWarnCalls[0][0] as { signal: number };
417
+ expect(warnObj.signal).toBe(9);
403
418
 
404
- warnSpy.mockRestore();
405
419
  spy.mockRestore();
406
420
  });
407
421
  });
@@ -67,7 +67,7 @@ mock.module("@atlas/api/lib/plugins/hooks", () => ({
67
67
 
68
68
  let mockSandboxPlugins: Array<{
69
69
  id: string;
70
- type: string;
70
+ types: string[];
71
71
  version: string;
72
72
  sandbox: {
73
73
  create(root: string): Promise<ExploreBackend> | ExploreBackend;
@@ -146,7 +146,7 @@ describe("explore sandbox plugin integration", () => {
146
146
  mockSandboxPlugins = [
147
147
  {
148
148
  id: "plugin-a",
149
- type: "sandbox",
149
+ types: ["sandbox"],
150
150
  version: "1.0.0",
151
151
  sandbox: { create: async () => backend },
152
152
  },
@@ -165,7 +165,7 @@ describe("explore sandbox plugin integration", () => {
165
165
  mockSandboxPlugins = [
166
166
  {
167
167
  id: "low-priority",
168
- type: "sandbox",
168
+ types: ["sandbox"],
169
169
  version: "1.0.0",
170
170
  sandbox: {
171
171
  create: async () => makeMockBackend("low"),
@@ -174,7 +174,7 @@ describe("explore sandbox plugin integration", () => {
174
174
  },
175
175
  {
176
176
  id: "high-priority",
177
- type: "sandbox",
177
+ types: ["sandbox"],
178
178
  version: "1.0.0",
179
179
  sandbox: {
180
180
  create: async () => makeMockBackend("high"),
@@ -196,7 +196,7 @@ describe("explore sandbox plugin integration", () => {
196
196
  mockSandboxPlugins = [
197
197
  {
198
198
  id: "broken-plugin",
199
- type: "sandbox",
199
+ types: ["sandbox"],
200
200
  version: "1.0.0",
201
201
  sandbox: {
202
202
  create: async () => { throw new Error("init failed"); },
@@ -205,7 +205,7 @@ describe("explore sandbox plugin integration", () => {
205
205
  },
206
206
  {
207
207
  id: "working-plugin",
208
- type: "sandbox",
208
+ types: ["sandbox"],
209
209
  version: "1.0.0",
210
210
  sandbox: {
211
211
  create: async () => makeMockBackend("working"),
@@ -228,7 +228,7 @@ describe("explore sandbox plugin integration", () => {
228
228
  mockSandboxPlugins = [
229
229
  {
230
230
  id: "broken-1",
231
- type: "sandbox",
231
+ types: ["sandbox"],
232
232
  version: "1.0.0",
233
233
  sandbox: {
234
234
  create: async () => { throw new Error("fail 1"); },
@@ -236,7 +236,7 @@ describe("explore sandbox plugin integration", () => {
236
236
  },
237
237
  {
238
238
  id: "broken-2",
239
- type: "sandbox",
239
+ types: ["sandbox"],
240
240
  version: "1.0.0",
241
241
  sandbox: {
242
242
  create: async () => { throw new Error("fail 2"); },
@@ -255,7 +255,7 @@ describe("explore sandbox plugin integration", () => {
255
255
  mockSandboxPlugins = [
256
256
  {
257
257
  id: "my-sandbox",
258
- type: "sandbox",
258
+ types: ["sandbox"],
259
259
  version: "2.0.0",
260
260
  sandbox: { create: async () => makeMockBackend("my-sandbox") },
261
261
  },
@@ -274,7 +274,7 @@ describe("explore sandbox plugin integration", () => {
274
274
  mockSandboxPlugins = [
275
275
  {
276
276
  id: "test-plugin",
277
- type: "sandbox",
277
+ types: ["sandbox"],
278
278
  version: "1.0.0",
279
279
  sandbox: { create: async () => makeMockBackend("test") },
280
280
  },
@@ -297,7 +297,7 @@ describe("explore sandbox plugin integration", () => {
297
297
  mockSandboxPlugins = [
298
298
  {
299
299
  id: "clearable-plugin",
300
- type: "sandbox",
300
+ types: ["sandbox"],
301
301
  version: "1.0.0",
302
302
  sandbox: { create: async () => makeMockBackend("clearable") },
303
303
  },
@@ -321,7 +321,7 @@ describe("explore sandbox plugin integration", () => {
321
321
  mockSandboxPlugins = [
322
322
  {
323
323
  id: "should-be-skipped",
324
- type: "sandbox",
324
+ types: ["sandbox"],
325
325
  version: "1.0.0",
326
326
  sandbox: { create: async () => makeMockBackend("skipped") },
327
327
  },
@@ -343,7 +343,7 @@ describe("explore sandbox plugin integration", () => {
343
343
  mockSandboxPlugins = [
344
344
  {
345
345
  id: "explicit-low",
346
- type: "sandbox",
346
+ types: ["sandbox"],
347
347
  version: "1.0.0",
348
348
  sandbox: {
349
349
  create: async () => makeMockBackend("explicit-low"),
@@ -352,7 +352,7 @@ describe("explore sandbox plugin integration", () => {
352
352
  },
353
353
  {
354
354
  id: "default-priority",
355
- type: "sandbox",
355
+ types: ["sandbox"],
356
356
  version: "1.0.0",
357
357
  sandbox: {
358
358
  // No priority — defaults to 60 (SANDBOX_DEFAULT_PRIORITY)