@useatlas/create 0.0.2 → 0.0.4

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
@@ -1,19 +1,7 @@
1
- import { describe, expect, it, afterEach, mock } from "bun:test";
1
+ import { describe, expect, it, afterEach } from "bun:test";
2
2
  import { tool } from "ai";
3
3
  import { z } from "zod";
4
4
 
5
- // Mock the Salesforce tool so the registry module can be imported
6
- // without needing jsforce or a real Salesforce connection.
7
- const mockSfTool = tool({
8
- description: "Mock querySalesforce",
9
- inputSchema: z.object({ soql: z.string() }),
10
- execute: async ({ soql }) => soql,
11
- });
12
-
13
- mock.module("@atlas/api/lib/tools/salesforce", () => ({
14
- querySalesforce: mockSfTool,
15
- }));
16
-
17
5
  const { ToolRegistry } = await import("@atlas/api/lib/tools/registry");
18
6
 
19
7
  function makeTool(name: string) {
@@ -2,18 +2,6 @@ import { describe, expect, it, mock } from "bun:test";
2
2
  import { tool } from "ai";
3
3
  import { z } from "zod";
4
4
 
5
- // Mock the Salesforce tool so buildRegistry({ includeSalesforce: true }) works
6
- // without needing jsforce or a real Salesforce connection.
7
- const mockSfTool = tool({
8
- description: "Mock querySalesforce",
9
- inputSchema: z.object({ soql: z.string() }),
10
- execute: async ({ soql }) => soql,
11
- });
12
-
13
- mock.module("@atlas/api/lib/tools/salesforce", () => ({
14
- querySalesforce: mockSfTool,
15
- }));
16
-
17
5
  // Mock the action tools so buildRegistry({ includeActions: true }) works
18
6
  // without needing JIRA/email credentials or external services.
19
7
  const mockJiraTool = tool({
@@ -179,18 +167,49 @@ describe("defaultRegistry", () => {
179
167
  });
180
168
 
181
169
  describe("buildRegistry", () => {
182
- it("without Salesforce returns 2 core tools", async () => {
170
+ it("throws when ATLAS_PYTHON_ENABLED=true but ATLAS_SANDBOX_URL is not set", async () => {
171
+ const saved = {
172
+ enabled: process.env.ATLAS_PYTHON_ENABLED,
173
+ url: process.env.ATLAS_SANDBOX_URL,
174
+ };
175
+ try {
176
+ process.env.ATLAS_PYTHON_ENABLED = "true";
177
+ delete process.env.ATLAS_SANDBOX_URL;
178
+ await expect(buildRegistry()).rejects.toThrow("ATLAS_SANDBOX_URL");
179
+ } finally {
180
+ if (saved.enabled !== undefined) process.env.ATLAS_PYTHON_ENABLED = saved.enabled;
181
+ else delete process.env.ATLAS_PYTHON_ENABLED;
182
+ if (saved.url !== undefined) process.env.ATLAS_SANDBOX_URL = saved.url;
183
+ else delete process.env.ATLAS_SANDBOX_URL;
184
+ }
185
+ });
186
+
187
+ it("includes executePython when ATLAS_PYTHON_ENABLED and ATLAS_SANDBOX_URL are set", async () => {
188
+ const saved = {
189
+ enabled: process.env.ATLAS_PYTHON_ENABLED,
190
+ url: process.env.ATLAS_SANDBOX_URL,
191
+ };
192
+ try {
193
+ process.env.ATLAS_PYTHON_ENABLED = "true";
194
+ process.env.ATLAS_SANDBOX_URL = "http://localhost:8080";
195
+ const registry = await buildRegistry();
196
+ const names = Object.keys(registry.getAll()).sort();
197
+ expect(names).toEqual(["executePython", "executeSQL", "explore"]);
198
+ expect(registry.describe()).toContain("### 4. Analyze Data with Python");
199
+ } finally {
200
+ if (saved.enabled !== undefined) process.env.ATLAS_PYTHON_ENABLED = saved.enabled;
201
+ else delete process.env.ATLAS_PYTHON_ENABLED;
202
+ if (saved.url !== undefined) process.env.ATLAS_SANDBOX_URL = saved.url;
203
+ else delete process.env.ATLAS_SANDBOX_URL;
204
+ }
205
+ });
206
+
207
+ it("returns 2 core tools by default", async () => {
183
208
  const registry = await buildRegistry();
184
209
  const names = Object.keys(registry.getAll()).sort();
185
210
  expect(names).toEqual(["executeSQL", "explore"]);
186
211
  });
187
212
 
188
- it("with includeSalesforce returns 3 tools including querySalesforce", async () => {
189
- const registry = await buildRegistry({ includeSalesforce: true });
190
- const names = Object.keys(registry.getAll()).sort();
191
- expect(names).toEqual(["executeSQL", "explore", "querySalesforce"]);
192
- });
193
-
194
213
  it("with includeActions returns 4 tools including createJiraTicket and sendEmailReport", async () => {
195
214
  const registry = await buildRegistry({ includeActions: true });
196
215
  const names = Object.keys(registry.getAll()).sort();
@@ -202,18 +221,6 @@ describe("buildRegistry", () => {
202
221
  ]);
203
222
  });
204
223
 
205
- it("with both includeSalesforce and includeActions returns 5 tools", async () => {
206
- const registry = await buildRegistry({ includeSalesforce: true, includeActions: true });
207
- const names = Object.keys(registry.getAll()).sort();
208
- expect(names).toEqual([
209
- "createJiraTicket",
210
- "executeSQL",
211
- "explore",
212
- "querySalesforce",
213
- "sendEmailReport",
214
- ]);
215
- });
216
-
217
224
  it("returned registry is frozen", async () => {
218
225
  const registry = await buildRegistry();
219
226
  expect(() =>
@@ -35,6 +35,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
35
35
  getDBType: () => "postgres",
36
36
  getTargetHost: () => "localhost",
37
37
  getValidator: () => undefined,
38
+ getParserDialect: () => undefined,
39
+ getForbiddenPatterns: () => [],
38
40
  list: () => ["default"],
39
41
  },
40
42
  detectDBType: () => "postgres",
@@ -42,6 +42,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
42
42
  return "postgres" as const;
43
43
  },
44
44
  getValidator: () => undefined,
45
+ getParserDialect: () => undefined,
46
+ getForbiddenPatterns: () => [],
45
47
  list: () => ["default", "warehouse"],
46
48
  describe: () => [
47
49
  { id: "default", dbType: "postgres" as const },
@@ -35,6 +35,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
35
35
  getDBType: () => "postgres",
36
36
  getTargetHost: () => "localhost",
37
37
  getValidator: () => undefined,
38
+ getParserDialect: () => undefined,
39
+ getForbiddenPatterns: () => [],
38
40
  list: () => ["default"],
39
41
  },
40
42
  detectDBType: () => "postgres",
@@ -27,18 +27,6 @@ const mockDetectDBType = () => {
27
27
  if (url.startsWith("mysql://") || url.startsWith("mysql2://")) {
28
28
  return "mysql";
29
29
  }
30
- if (url.startsWith("clickhouse://")) {
31
- return "clickhouse";
32
- }
33
- if (url.startsWith("snowflake://")) {
34
- return "snowflake";
35
- }
36
- if (url.startsWith("duckdb://")) {
37
- return "duckdb";
38
- }
39
- if (url.startsWith("salesforce://")) {
40
- return "salesforce";
41
- }
42
30
  throw new Error(`Unsupported database URL: "${url.slice(0, 40)}…".`);
43
31
  };
44
32
 
@@ -49,6 +37,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
49
37
  getDefault: () => mockDBConnection,
50
38
  getDBType: () => mockDetectDBType(),
51
39
  getValidator: () => undefined,
40
+ getParserDialect: () => undefined,
41
+ getForbiddenPatterns: () => [],
52
42
  list: () => ["default"],
53
43
  },
54
44
  detectDBType: mockDetectDBType,
@@ -688,37 +678,6 @@ describe("validateSQL", () => {
688
678
  });
689
679
  });
690
680
 
691
- // ----- Cross-DB guard: Snowflake patterns don't fire in PostgreSQL mode ---
692
-
693
- describe("cross-database regex guard isolation — Snowflake patterns", () => {
694
- it("does not reject GET in PostgreSQL mode via regex guard", () => {
695
- process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
696
- const result = validateSQL("GET @mystage file:///tmp/");
697
- expect(result.valid).toBe(false);
698
- expect(result.error).not.toContain("Forbidden SQL operation detected");
699
- });
700
-
701
- it("does not reject LIST in PostgreSQL mode via regex guard", () => {
702
- process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
703
- const result = validateSQL("LIST @mystage");
704
- expect(result.valid).toBe(false);
705
- expect(result.error).not.toContain("Forbidden SQL operation detected");
706
- });
707
-
708
- it("does not reject MERGE in MySQL mode via regex guard", () => {
709
- process.env.ATLAS_DATASOURCE_URL = "mysql://test:test@localhost:3306/test";
710
- // Minimal MERGE without UPDATE/INSERT keywords to isolate Snowflake-specific regex
711
- const result = validateSQL("MERGE INTO companies USING src ON companies.id = src.id");
712
- expect(result.valid).toBe(false);
713
- expect(result.error).not.toContain("Forbidden SQL operation detected");
714
- });
715
-
716
- it("still blocks PUT in Snowflake mode via regex guard", () => {
717
- process.env.ATLAS_DATASOURCE_URL = "snowflake://user:pass@account123/db/schema";
718
- expectInvalid("PUT file:///tmp/data.csv @mystage", "forbidden");
719
- });
720
- });
721
-
722
681
  // ----- Formerly SQLite commands still rejected by AST ----------------------
723
682
 
724
683
  describe("formerly SQLite-specific commands still rejected via AST", () => {
@@ -744,269 +703,7 @@ describe("validateSQL", () => {
744
703
  });
745
704
  });
746
705
 
747
- // ----- ClickHouse-specific validation ----------------------------------------
748
-
749
- describe("ClickHouse-specific", () => {
750
- beforeEach(() => {
751
- process.env.ATLAS_DATASOURCE_URL = "clickhouse://test:test@localhost:8123/default";
752
- });
753
-
754
- it("rejects OPTIMIZE statement", () => {
755
- expectInvalid("OPTIMIZE TABLE companies", "forbidden");
756
- });
757
-
758
- it("rejects SYSTEM statement", () => {
759
- expectInvalid("SYSTEM FLUSH LOGS", "forbidden");
760
- });
761
-
762
- it("rejects KILL statement", () => {
763
- expectInvalid("KILL QUERY WHERE query_id = '123'", "forbidden");
764
- });
765
-
766
- it("rejects ATTACH statement", () => {
767
- expectInvalid("ATTACH TABLE companies", "forbidden");
768
- });
769
-
770
- it("rejects DETACH statement", () => {
771
- expectInvalid("DETACH TABLE companies", "forbidden");
772
- });
773
-
774
- it("rejects RENAME statement", () => {
775
- expectInvalid("RENAME TABLE companies TO old_companies", "forbidden");
776
- });
777
-
778
- it("rejects EXCHANGE statement", () => {
779
- expectInvalid("EXCHANGE TABLES companies AND new_companies", "forbidden");
780
- });
781
-
782
- it("rejects SHOW statement", () => {
783
- expectInvalid("SHOW TABLES", "forbidden");
784
- });
785
-
786
- it("rejects DESCRIBE statement", () => {
787
- expectInvalid("DESCRIBE companies", "forbidden");
788
- });
789
-
790
- it("rejects mixed-case OPTIMIZE", () => {
791
- expectInvalid("OpTiMiZe TABLE companies", "forbidden");
792
- });
793
-
794
- it("rejects mixed-case SYSTEM", () => {
795
- expectInvalid("SyStEm FLUSH LOGS", "forbidden");
796
- });
797
-
798
- it("still rejects base forbidden patterns (INSERT, UPDATE, DELETE, DROP)", () => {
799
- expectInvalid("INSERT INTO companies (name) VALUES ('x')", "forbidden");
800
- expectInvalid("DROP TABLE companies", "forbidden");
801
- });
802
- });
803
-
804
- // ----- ClickHouse parser mode -------------------------------------------------
805
-
806
- describe("ClickHouse parser mode", () => {
807
- beforeEach(() => {
808
- process.env.ATLAS_DATASOURCE_URL = "clickhouse://test:test@localhost:8123/default";
809
- });
810
-
811
- it("accepts a simple SELECT in ClickHouse mode", () => {
812
- expectValid("SELECT id, name FROM companies");
813
- });
814
-
815
- it("accepts SELECT with JOIN in ClickHouse mode", () => {
816
- expectValid(
817
- "SELECT c.name, p.name FROM companies c JOIN people p ON c.id = p.company_id"
818
- );
819
- });
820
-
821
- it("accepts CTEs in ClickHouse mode", () => {
822
- expectValid(
823
- "WITH top AS (SELECT id, name FROM companies LIMIT 10) SELECT * FROM top"
824
- );
825
- });
826
-
827
- it("accepts subqueries in ClickHouse mode", () => {
828
- expectValid(
829
- "SELECT * FROM companies WHERE id IN (SELECT company_id FROM people)"
830
- );
831
- });
832
-
833
- it("rejects INSERT in ClickHouse mode", () => {
834
- expectInvalid(
835
- "INSERT INTO companies (name) VALUES ('Evil')",
836
- "forbidden"
837
- );
838
- });
839
-
840
- it("rejects UPDATE in ClickHouse mode", () => {
841
- expectInvalid("UPDATE companies SET name = 'Evil'", "forbidden");
842
- });
843
-
844
- it("rejects DELETE in ClickHouse mode", () => {
845
- expectInvalid("DELETE FROM companies WHERE id = 1", "forbidden");
846
- });
847
-
848
- it("rejects non-whitelisted tables in ClickHouse mode", () => {
849
- expectInvalid(
850
- "SELECT * FROM secret_data",
851
- "not in the allowed list"
852
- );
853
- });
854
-
855
- it("does not reject CTE names as non-whitelisted tables in ClickHouse mode", () => {
856
- expectValid(
857
- "WITH my_temp AS (SELECT id FROM companies) SELECT * FROM my_temp"
858
- );
859
- });
860
-
861
- it("accepts UNION of SELECTs in ClickHouse mode", () => {
862
- expectValid(
863
- "SELECT name FROM companies UNION ALL SELECT name FROM people"
864
- );
865
- });
866
- });
867
-
868
- // ----- Cross-DB guard: ClickHouse patterns don't fire in PostgreSQL mode ------
869
-
870
- describe("cross-database regex guard isolation (ClickHouse)", () => {
871
- it("rejects OPTIMIZE in PostgreSQL mode via regex guard (base pattern)", () => {
872
- process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
873
- expectInvalid("OPTIMIZE TABLE companies", "forbidden");
874
- });
875
-
876
- it("rejects SYSTEM in PostgreSQL mode via AST parser, not regex guard", () => {
877
- process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
878
- const result = validateSQL("SYSTEM FLUSH LOGS");
879
- expect(result.valid).toBe(false);
880
- expect(result.error).not.toContain("Forbidden SQL operation detected");
881
- });
882
-
883
- it("still blocks OPTIMIZE in ClickHouse mode via regex guard", () => {
884
- process.env.ATLAS_DATASOURCE_URL = "clickhouse://test:test@localhost:8123/default";
885
- expectInvalid("OPTIMIZE TABLE companies", "forbidden");
886
- });
887
-
888
- it("rejects OPTIMIZE in MySQL mode via regex guard (base pattern)", () => {
889
- process.env.ATLAS_DATASOURCE_URL = "mysql://test:test@localhost:3306/test";
890
- expectInvalid("OPTIMIZE TABLE companies", "forbidden");
891
- });
892
- });
893
-
894
- // ----- Snowflake-specific validation -------------------------------------------
895
-
896
- describe("Snowflake-specific", () => {
897
- beforeEach(() => {
898
- process.env.ATLAS_DATASOURCE_URL = "snowflake://user:pass@account123/db/schema";
899
- });
900
-
901
- it("rejects PUT stage operation", () => {
902
- expectInvalid("PUT file:///tmp/data.csv @mystage", "forbidden");
903
- });
904
-
905
- it("rejects GET stage operation", () => {
906
- expectInvalid("GET @mystage file:///tmp/", "forbidden");
907
- });
908
-
909
- it("rejects LIST stage operation", () => {
910
- expectInvalid("LIST @mystage", "forbidden");
911
- });
912
-
913
- it("rejects REMOVE stage operation", () => {
914
- expectInvalid("REMOVE @mystage/file.csv", "forbidden");
915
- });
916
-
917
- it("rejects RM stage operation", () => {
918
- expectInvalid("RM @mystage/file.csv", "forbidden");
919
- });
920
-
921
- it("rejects COPY INTO (Snowflake data ingestion)", () => {
922
- expectInvalid("COPY INTO my_table FROM @my_stage", "forbidden");
923
- });
924
-
925
- it("rejects MERGE statement", () => {
926
- expectInvalid(
927
- "MERGE INTO companies USING new_data ON companies.id = new_data.id WHEN MATCHED THEN UPDATE SET name = new_data.name",
928
- "forbidden"
929
- );
930
- });
931
-
932
- it("rejects SHOW TABLES", () => {
933
- expectInvalid("SHOW TABLES", "forbidden");
934
- });
935
-
936
- it("rejects DESCRIBE", () => {
937
- expectInvalid("DESCRIBE companies", "forbidden");
938
- });
939
-
940
- it("rejects USE statement", () => {
941
- expectInvalid("USE DATABASE mydb", "forbidden");
942
- });
943
-
944
- it("accepts valid SELECT in Snowflake mode", () => {
945
- expectValid("SELECT * FROM companies LIMIT 10");
946
- });
947
-
948
- it("accepts SELECT with Snowflake-style identifier quoting", () => {
949
- expectValid('SELECT "name", "id" FROM companies WHERE "id" > 0');
950
- });
951
-
952
- it("accepts CTEs in Snowflake mode", () => {
953
- expectValid("WITH top AS (SELECT id, name FROM companies LIMIT 10) SELECT * FROM top");
954
- });
955
-
956
- it("accepts SELECT with JOIN in Snowflake mode", () => {
957
- expectValid("SELECT c.name, p.name FROM companies c JOIN people p ON c.id = p.company_id");
958
- });
959
-
960
- it("accepts subqueries in Snowflake mode", () => {
961
- expectValid("SELECT * FROM companies WHERE id IN (SELECT company_id FROM people)");
962
- });
963
-
964
- it("accepts window functions in Snowflake mode", () => {
965
- expectValid("SELECT name, ROW_NUMBER() OVER (ORDER BY id) FROM companies");
966
- });
967
-
968
- it("does not reject CTE names as non-whitelisted tables in Snowflake mode", () => {
969
- expectValid("WITH my_temp AS (SELECT id FROM companies) SELECT * FROM my_temp");
970
- });
971
-
972
- it("rejects non-whitelisted tables in Snowflake mode", () => {
973
- expectInvalid("SELECT * FROM secret_data", "not in the allowed list");
974
- });
975
-
976
- it("accepts UNION of SELECTs in Snowflake mode", () => {
977
- expectValid("SELECT name FROM companies UNION ALL SELECT name FROM people");
978
- });
979
-
980
- it("rejects INSERT in Snowflake mode", () => {
981
- expectInvalid("INSERT INTO companies (name) VALUES ('Evil')", "forbidden");
982
- });
983
-
984
- it("rejects EXPLAIN in Snowflake mode", () => {
985
- expectInvalid("EXPLAIN SELECT * FROM companies", "forbidden");
986
- });
987
-
988
- it("rejects mixed-case PUT in Snowflake mode", () => {
989
- expectInvalid("PuT file:///tmp/data.csv @mystage", "forbidden");
990
- });
991
-
992
- it("allows data values containing 'Get' (no false positive after anchoring)", () => {
993
- expectValid("SELECT id, name FROM companies WHERE name = 'Get Ready'");
994
- });
995
-
996
- it("allows column named 'list' (no false positive after anchoring)", () => {
997
- expectValid("SELECT id FROM companies WHERE list = true");
998
- });
999
- });
1000
-
1001
- describe("Salesforce redirect", () => {
1002
- beforeEach(() => {
1003
- process.env.ATLAS_DATASOURCE_URL = "salesforce://user:pass@login.salesforce.com";
1004
- });
1005
-
1006
- it("rejects any SQL and directs to querySalesforce tool", () => {
1007
- const result = validateSQL("SELECT Id FROM Account");
1008
- expect(result.valid).toBe(false);
1009
- expect(result.error).toContain("querySalesforce");
1010
- });
1011
- });
706
+ // Note: ClickHouse, DuckDB, and Snowflake-specific validation tests were removed
707
+ // because those adapters are now plugins. See plugins/{clickhouse,snowflake,duckdb}-datasource/.
1012
708
  });
709
+
@@ -11,8 +11,11 @@
11
11
  */
12
12
 
13
13
  import type { ExploreBackend, ExecResult } from "./explore";
14
+ import { createLogger } from "@atlas/api/lib/logger";
14
15
  import * as fs from "fs";
15
16
 
17
+ const log = createLogger("nsjail-sandbox");
18
+
16
19
  /** Maximum bytes to read from stdout/stderr (1 MB). */
17
20
  const MAX_OUTPUT = 1024 * 1024;
18
21
 
@@ -51,8 +54,8 @@ function parsePositiveInt(
51
54
  if (raw === undefined) return defaultValue;
52
55
  const parsed = parseInt(raw, 10);
53
56
  if (isNaN(parsed) || parsed <= 0) {
54
- console.warn(
55
- `[atlas] Invalid ${envVar}="${raw}" for ${name}, using default: ${defaultValue}`,
57
+ log.warn(
58
+ `Invalid ${envVar}="${raw}" for ${name}, using default: ${defaultValue}`,
56
59
  );
57
60
  return defaultValue;
58
61
  }
@@ -72,8 +75,8 @@ export function findNsjailBinary(): string | null {
72
75
  err instanceof Error && "code" in err
73
76
  ? (err as NodeJS.ErrnoException).code
74
77
  : "unknown";
75
- console.error(
76
- `[atlas] ATLAS_NSJAIL_PATH="${explicit}" is not executable (${code})`,
78
+ log.error(
79
+ `ATLAS_NSJAIL_PATH="${explicit}" is not executable (${code})`,
77
80
  );
78
81
  return null;
79
82
  }
@@ -294,7 +297,7 @@ export async function createNsjailBackend(
294
297
  } catch (err) {
295
298
  // Spawn itself failed — infrastructure error
296
299
  const detail = err instanceof Error ? err.message : String(err);
297
- console.error("[atlas] nsjail spawn failed:", detail);
300
+ log.error({ err: detail }, "nsjail spawn failed");
298
301
  callbacks.onInfrastructureError();
299
302
  throw new Error(
300
303
  `nsjail infrastructure error: ${detail}. Backend cache cleared; nsjail will be re-initialized on next explore call.`,
@@ -311,8 +314,9 @@ export async function createNsjailBackend(
311
314
  exitCode = await proc.exited;
312
315
  } catch (err) {
313
316
  const detail = err instanceof Error ? err.message : String(err);
314
- console.error(
315
- `[atlas] nsjail process I/O error: ${detail} | command: ${command}`,
317
+ log.error(
318
+ { err: detail, command },
319
+ "nsjail process I/O error",
316
320
  );
317
321
  throw new Error(
318
322
  `nsjail process I/O error: ${detail}`,
@@ -322,9 +326,9 @@ export async function createNsjailBackend(
322
326
 
323
327
  // Interpret nsjail-specific exit codes
324
328
  if (exitCode === 109) {
325
- console.error(
326
- "[atlas] nsjail setup failure (exit 109) — sandbox may not have been applied. stderr:",
327
- stderr,
329
+ log.error(
330
+ { exitCode, stderr },
331
+ "nsjail setup failure (exit 109) — sandbox may not have been applied",
328
332
  );
329
333
  // Mark nsjail as permanently failed so the system falls back to just-bash
330
334
  // (when ATLAS_SANDBOX=nsjail, getExploreBackend will still throw hard)
@@ -332,8 +336,9 @@ export async function createNsjailBackend(
332
336
  }
333
337
  if (exitCode > 128) {
334
338
  const signal = exitCode - 128;
335
- console.warn(
336
- `[atlas] nsjail child killed by signal ${signal} | command: ${command}`,
339
+ log.warn(
340
+ { signal, command },
341
+ "nsjail child killed by signal",
337
342
  );
338
343
  }
339
344
 
@@ -37,6 +37,31 @@ export async function createSidecarBackend(
37
37
  }
38
38
 
39
39
  const execUrl = new URL("/exec", baseUrl).toString();
40
+ const healthUrl = new URL("/health", baseUrl).toString();
41
+
42
+ // Lightweight health check on first creation — validates the sidecar is
43
+ // reachable before we return the backend. This catches misconfigured URLs
44
+ // and down services early (at backend init) rather than on the first user
45
+ // command, which produces a better error experience.
46
+ try {
47
+ const healthRes = await fetch(healthUrl, {
48
+ signal: AbortSignal.timeout(5_000),
49
+ });
50
+ if (!healthRes.ok) {
51
+ log.warn(
52
+ { status: healthRes.status, url: healthUrl },
53
+ "Sidecar health check returned non-OK status — commands may fail",
54
+ );
55
+ } else {
56
+ log.info({ url: baseUrl.origin }, "Sidecar health check passed");
57
+ }
58
+ } catch (err) {
59
+ const detail = err instanceof Error ? err.message : String(err);
60
+ log.warn(
61
+ { err: detail, url: healthUrl },
62
+ "Sidecar health check failed on init — sidecar may be starting up or unreachable",
63
+ );
64
+ }
40
65
 
41
66
  return {
42
67
  exec: async (command: string): Promise<ExecResult> => {