@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
@@ -6,7 +6,7 @@
6
6
  * - sandbox plugin: pluggable explore backend via the Plugin SDK (priority-sorted)
7
7
  * - @vercel/sandbox: ephemeral microVM with networkPolicy "deny-all" (Vercel)
8
8
  * - nsjail: Linux namespace sandbox (self-hosted Docker)
9
- * - sidecar: HTTP-isolated container with no secrets (Railway/Render)
9
+ * - sidecar: HTTP-isolated container with no secrets (Railway)
10
10
  * - just-bash: OverlayFs ensures read-only access; writes stay in memory (dev, or production fallback)
11
11
  *
12
12
  * Runtime selection priority: sandbox plugin > Vercel sandbox > nsjail (explicit) > sidecar > nsjail (auto-detect) > just-bash.
@@ -19,9 +19,6 @@ import * as path from "path";
19
19
  import { createLogger } from "@atlas/api/lib/logger";
20
20
  import { withSpan } from "@atlas/api/lib/tracing";
21
21
 
22
- /** Must match SANDBOX_DEFAULT_PRIORITY in @useatlas/plugin-sdk/types. */
23
- const SANDBOX_DEFAULT_PRIORITY = 60;
24
-
25
22
  const log = createLogger("explore");
26
23
 
27
24
  const SEMANTIC_ROOT = path.resolve(process.cwd(), "semantic");
@@ -108,7 +105,8 @@ function useNsjail(): boolean {
108
105
  _nsjailAvailable = isNsjailAvailable();
109
106
  } catch (err) {
110
107
  if (
111
- err instanceof Error &&
108
+ err != null &&
109
+ typeof err === "object" &&
112
110
  "code" in err &&
113
111
  (err as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND"
114
112
  ) {
@@ -156,7 +154,7 @@ export function getExploreBackendType(): ExploreBackendType {
156
154
  if (useVercelSandbox()) return "vercel-sandbox";
157
155
  // Explicit nsjail (ATLAS_SANDBOX=nsjail) — hard-fail if unavailable
158
156
  if (process.env.ATLAS_SANDBOX === "nsjail" && !_nsjailFailed) return "nsjail";
159
- // Sidecar takes priority over nsjail auto-detection (Railway/Render set ATLAS_SANDBOX_URL)
157
+ // Sidecar takes priority over nsjail auto-detection (Railway sets ATLAS_SANDBOX_URL)
160
158
  if (useSidecar() && !_sidecarFailed) return "sidecar";
161
159
  // nsjail auto-detect (binary on PATH)
162
160
  if (!_nsjailFailed && useNsjail()) return "nsjail";
@@ -190,35 +188,29 @@ function getExploreBackend(): Promise<ExploreBackend> {
190
188
  // Priority 0: Sandbox plugins (sorted by priority, highest first)
191
189
  // Skipped when ATLAS_SANDBOX=nsjail — operator explicitly wants nsjail only
192
190
  if (process.env.ATLAS_SANDBOX !== "nsjail") {
193
- let sandboxPlugins: Array<{ id: string; [k: string]: unknown }> = [];
194
191
  try {
195
192
  const { plugins } = await import("@atlas/api/lib/plugins/registry");
196
- sandboxPlugins = plugins.getByType("sandbox");
193
+ const { wireSandboxPlugins } = await import("@atlas/api/lib/plugins/wiring");
194
+ const result = await wireSandboxPlugins(plugins, SEMANTIC_ROOT);
195
+ if (result.failed.length > 0) {
196
+ log.warn(
197
+ { failed: result.failed, selectedPlugin: result.pluginId },
198
+ "Some sandbox plugins failed during create()",
199
+ );
200
+ }
201
+ if (result.backend) {
202
+ _activeSandboxPluginId = result.pluginId;
203
+ return result.backend as ExploreBackend;
204
+ }
197
205
  } catch (err) {
198
206
  const detail = err instanceof Error ? err.message : String(err);
199
- log.debug({ err: detail }, "Plugin registry not available for sandbox check");
200
- }
201
-
202
- if (sandboxPlugins.length > 0) {
203
- type SandboxShape = { sandbox: { create(root: string): Promise<ExploreBackend> | ExploreBackend; priority?: number } };
204
- const sorted = [...sandboxPlugins].sort((a, b) => {
205
- const pa = (a as unknown as SandboxShape).sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
206
- const pb = (b as unknown as SandboxShape).sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
207
- return pb - pa;
208
- });
209
- for (const sp of sorted) {
210
- const sandbox = (sp as unknown as SandboxShape).sandbox;
211
- try {
212
- const backend = await sandbox.create(SEMANTIC_ROOT);
213
- _activeSandboxPluginId = sp.id;
214
- log.info({ pluginId: sp.id }, "Using sandbox plugin for explore backend");
215
- return backend;
216
- } catch (err) {
217
- const detail = err instanceof Error ? err.message : String(err);
218
- log.error({ pluginId: sp.id, err: detail }, "Sandbox plugin create() failed, trying next");
219
- }
207
+ const isModuleError = err != null && typeof err === "object" && "code" in err
208
+ && (err as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND";
209
+ if (isModuleError) {
210
+ log.debug({ err: detail }, "Plugin modules not available — skipping sandbox plugins");
211
+ } else {
212
+ log.error({ err: detail }, "Unexpected error during sandbox plugin wiring");
220
213
  }
221
- log.error({ count: sorted.length }, "All sandbox plugins failed to create a backend");
222
214
  }
223
215
  }
224
216
 
@@ -247,7 +239,7 @@ function getExploreBackend(): Promise<ExploreBackend> {
247
239
  }
248
240
 
249
241
  // Priority 3: Sidecar service (HTTP-isolated microservice)
250
- // When ATLAS_SANDBOX_URL is set, sidecar is the intended backend (Railway/Render).
242
+ // When ATLAS_SANDBOX_URL is set, sidecar is the intended backend (Railway).
251
243
  // Skips nsjail auto-detection entirely — no noisy namespace warnings.
252
244
  if (useSidecar()) {
253
245
  const { createSidecarBackend } = await import("./explore-sidecar");
@@ -275,10 +267,14 @@ function getExploreBackend(): Promise<ExploreBackend> {
275
267
  // Priority 5: just-bash (no process isolation)
276
268
  if (process.env.NODE_ENV === "production") {
277
269
  log.warn(
278
- "Explore tool running without process isolation. " +
270
+ "SECURITY DEGRADATION: Explore tool running without process isolation (just-bash fallback). " +
271
+ "In production, this means shell commands execute directly on the host with only OverlayFs " +
272
+ "read-only protection — no namespace, network, or resource isolation. " +
279
273
  "Install nsjail, configure a sidecar (ATLAS_SANDBOX_URL), or deploy on Vercel for sandboxed execution. " +
280
274
  "See: https://github.com/google/nsjail",
281
275
  );
276
+ } else {
277
+ log.debug("Explore tool using just-bash backend (acceptable for development)");
282
278
  }
283
279
  return createBashBackend(SEMANTIC_ROOT);
284
280
  })().catch((err) => {
@@ -0,0 +1,396 @@
1
+ /**
2
+ * nsjail backend for the Python execution tool.
3
+ *
4
+ * Uses nsjail (Linux namespaces) to run Python code in a sandboxed process.
5
+ * Follows the same pattern as explore-nsjail.ts but with Python-specific
6
+ * configuration: bind-mounted Python runtime, data injection via stdin,
7
+ * chart collection from tmpfs, and higher resource limits.
8
+ *
9
+ * Security: no network (nsjail default), no host secrets, runs as nobody
10
+ * (65534:65534), code + data injected via tmpfs files and stdin.
11
+ */
12
+
13
+ import type { PythonBackend, PythonResult } from "./python";
14
+ import { randomUUID } from "crypto";
15
+ import { mkdirSync, writeFileSync, rmSync } from "fs";
16
+ import { join } from "path";
17
+ import { createLogger } from "@atlas/api/lib/logger";
18
+
19
+ const log = createLogger("python-nsjail");
20
+
21
+ /** Maximum bytes to read from stdout/stderr (1 MB). */
22
+ const MAX_OUTPUT = 1024 * 1024;
23
+
24
+ /** Default Python execution timeout in seconds. */
25
+ const DEFAULT_TIME_LIMIT = 30;
26
+
27
+ /** Default memory limit in MB. */
28
+ const DEFAULT_MEMORY_LIMIT = 512;
29
+
30
+ /** Default max processes. */
31
+ const DEFAULT_NPROC = 16;
32
+
33
+ /**
34
+ * Python wrapper script — same logic as the sidecar's PYTHON_WRAPPER.
35
+ *
36
+ * Handles: import guard (sidecar-side enforcement), data injection
37
+ * (JSON on stdin → DataFrame/dict), stdout capture, chart collection
38
+ * (PNG files + Recharts dicts), and structured output via result marker.
39
+ */
40
+ const PYTHON_WRAPPER = `
41
+ import sys, json, io, base64, glob, os, ast
42
+
43
+ _marker = os.environ["ATLAS_RESULT_MARKER"]
44
+ _chart_dir = os.environ.get("ATLAS_CHART_DIR", "/tmp")
45
+
46
+ # --- Import guard (sidecar-side enforcement) ---
47
+ _BLOCKED_MODULES = {
48
+ "subprocess", "os", "socket", "shutil", "sys", "ctypes", "importlib",
49
+ "code", "signal", "multiprocessing", "threading", "pty", "fcntl",
50
+ "termios", "resource", "posixpath",
51
+ "http", "urllib", "requests", "httpx", "aiohttp", "webbrowser",
52
+ "pickle", "tempfile", "pathlib",
53
+ }
54
+ _BLOCKED_BUILTINS = {
55
+ "compile", "exec", "eval", "__import__", "open", "breakpoint",
56
+ "getattr", "globals", "locals", "vars", "dir", "delattr", "setattr",
57
+ }
58
+
59
+ _user_code = open(sys.argv[1]).read()
60
+ try:
61
+ _tree = ast.parse(_user_code)
62
+ except SyntaxError as e:
63
+ print(_marker + json.dumps({"success": False, "error": f"SyntaxError: {e.msg} (line {e.lineno})"}))
64
+ sys.exit(0)
65
+
66
+ _blocked = None
67
+ for _node in ast.walk(_tree):
68
+ if _blocked:
69
+ break
70
+ if isinstance(_node, ast.Import):
71
+ for _alias in _node.names:
72
+ _mod = _alias.name.split('.')[0]
73
+ if _mod in _BLOCKED_MODULES:
74
+ _blocked = f'Blocked import: "{_mod}" is not allowed'
75
+ break
76
+ elif isinstance(_node, ast.ImportFrom):
77
+ if _node.module:
78
+ _mod = _node.module.split('.')[0]
79
+ if _mod in _BLOCKED_MODULES:
80
+ _blocked = f'Blocked import: "{_mod}" is not allowed'
81
+ elif isinstance(_node, ast.Call):
82
+ _name = None
83
+ if isinstance(_node.func, ast.Name):
84
+ _name = _node.func.id
85
+ elif isinstance(_node.func, ast.Attribute):
86
+ _name = _node.func.attr
87
+ if _name and _name in _BLOCKED_BUILTINS:
88
+ _blocked = f'Blocked builtin: "{_name}()" is not allowed'
89
+
90
+ if _blocked:
91
+ print(_marker + json.dumps({"success": False, "error": _blocked}))
92
+ sys.exit(0)
93
+
94
+ # --- Data injection ---
95
+ _stdin_data = sys.stdin.read()
96
+ _atlas_data = None
97
+ if _stdin_data.strip():
98
+ _atlas_data = json.loads(_stdin_data)
99
+
100
+ data = None
101
+ df = None
102
+ if _atlas_data:
103
+ try:
104
+ import pandas as pd
105
+ df = pd.DataFrame(_atlas_data["rows"], columns=_atlas_data["columns"])
106
+ data = df
107
+ except ImportError:
108
+ data = _atlas_data
109
+
110
+ # Configure matplotlib for headless rendering
111
+ try:
112
+ import matplotlib
113
+ matplotlib.use('Agg')
114
+ except ImportError:
115
+ pass
116
+
117
+ def chart_path(n=0):
118
+ return os.path.join(_chart_dir, f"chart_{n}.png")
119
+
120
+ # --- Execute user code in isolated namespace ---
121
+ _old_stdout = sys.stdout
122
+ sys.stdout = _captured = io.StringIO()
123
+
124
+ _user_ns = {"chart_path": chart_path, "data": data, "df": df}
125
+ _atlas_error = None
126
+ try:
127
+ exec(_user_code, _user_ns)
128
+ except Exception as e:
129
+ _atlas_error = f"{type(e).__name__}: {e}"
130
+
131
+ _output = _captured.getvalue()
132
+ sys.stdout = _old_stdout
133
+
134
+ # --- Collect results ---
135
+ _charts = []
136
+ for f in sorted(glob.glob(os.path.join(_chart_dir, "chart_*.png"))):
137
+ with open(f, "rb") as fh:
138
+ _charts.append({"base64": base64.b64encode(fh.read()).decode(), "mimeType": "image/png"})
139
+
140
+ _result = {"success": _atlas_error is None}
141
+ if _output.strip():
142
+ _result["output"] = _output.strip()
143
+ if _atlas_error:
144
+ _result["error"] = _atlas_error
145
+
146
+ if "_atlas_table" in _user_ns:
147
+ _result["table"] = _user_ns["_atlas_table"]
148
+
149
+ if "_atlas_chart" in _user_ns:
150
+ _ac = _user_ns["_atlas_chart"]
151
+ if isinstance(_ac, dict):
152
+ _result["rechartsCharts"] = [_ac]
153
+ elif isinstance(_ac, list):
154
+ _result["rechartsCharts"] = _ac
155
+
156
+ if _charts:
157
+ _result["charts"] = _charts
158
+
159
+ print(_marker + json.dumps(_result), file=_old_stdout)
160
+ `;
161
+
162
+ /** Read up to `max` bytes from a stream. */
163
+ async function readLimited(stream: ReadableStream, max: number): Promise<string> {
164
+ const reader = stream.getReader();
165
+ const chunks: Uint8Array[] = [];
166
+ let total = 0;
167
+ try {
168
+ while (true) {
169
+ const { done, value } = await reader.read();
170
+ if (done) break;
171
+ total += value.byteLength;
172
+ if (total > max) {
173
+ chunks.push(value.slice(0, max - (total - value.byteLength)));
174
+ break;
175
+ }
176
+ chunks.push(value);
177
+ }
178
+ } finally {
179
+ await reader.cancel().catch(() => {});
180
+ }
181
+ return new TextDecoder().decode(Buffer.concat(chunks));
182
+ }
183
+
184
+ /** Parse a positive integer from an env var, returning defaultValue on invalid input. */
185
+ function parsePositiveInt(envVar: string, defaultValue: number, name: string): number {
186
+ const raw = process.env[envVar];
187
+ if (raw === undefined) return defaultValue;
188
+ const parsed = parseInt(raw, 10);
189
+ if (isNaN(parsed) || parsed <= 0) {
190
+ log.warn({ envVar, raw, default: defaultValue }, `Invalid ${envVar} for ${name}, using default`);
191
+ return defaultValue;
192
+ }
193
+ return parsed;
194
+ }
195
+
196
+ /** Build nsjail args for Python execution. */
197
+ export function buildPythonNsjailArgs(
198
+ nsjailPath: string,
199
+ tmpDir: string,
200
+ codeFile: string,
201
+ wrapperFile: string,
202
+ chartDir: string,
203
+ _resultMarker: string,
204
+ ): string[] {
205
+ const timeLimit = parsePositiveInt("ATLAS_NSJAIL_TIME_LIMIT", DEFAULT_TIME_LIMIT, "time limit");
206
+ const memoryLimit = parsePositiveInt("ATLAS_NSJAIL_MEMORY_LIMIT", DEFAULT_MEMORY_LIMIT, "memory limit");
207
+ const nproc = DEFAULT_NPROC;
208
+
209
+ return [
210
+ nsjailPath,
211
+ "--mode", "o",
212
+
213
+ // Read-only bind mounts: system libs + Python runtime
214
+ "-R", "/bin",
215
+ "-R", "/usr/bin",
216
+ "-R", "/usr/local/bin",
217
+ "-R", "/lib",
218
+ "-R", "/lib64",
219
+ "-R", "/usr/lib",
220
+ "-R", "/usr/local/lib",
221
+
222
+ // Minimal /dev
223
+ "-R", "/dev/null",
224
+ "-R", "/dev/zero",
225
+ "-R", "/dev/urandom",
226
+
227
+ // /proc for correct namespace operation
228
+ "--proc_path", "/proc",
229
+
230
+ // Writable tmpfs for scratch
231
+ "-T", "/tmp",
232
+
233
+ // Bind-mount code files and chart dir into the jail (read-write for charts)
234
+ "-R", `${wrapperFile}:/tmp/wrapper.py`,
235
+ "-R", `${codeFile}:/tmp/user_code.py`,
236
+ "-B", `${chartDir}:/tmp/charts`,
237
+
238
+ // Working directory
239
+ "--cwd", "/tmp",
240
+
241
+ // Time limit
242
+ "-t", String(timeLimit),
243
+
244
+ // Resource limits (higher than explore for data science workloads)
245
+ "--rlimit_as", String(memoryLimit),
246
+ "--rlimit_fsize", "50", // 50 MB for chart output
247
+ "--rlimit_nproc", String(nproc),
248
+ "--rlimit_nofile", "128",
249
+
250
+ // Run as nobody
251
+ "-u", "65534",
252
+ "-g", "65534",
253
+
254
+ // Pass stdin through
255
+ "--pass_fd", "0",
256
+
257
+ // Suppress nsjail info logs
258
+ "--quiet",
259
+
260
+ // Command: python3 wrapper.py user_code.py
261
+ "--",
262
+ "/usr/bin/python3", "/tmp/wrapper.py", "/tmp/user_code.py",
263
+ ];
264
+ }
265
+
266
+ /** Minimal env for the Python jail — no secrets. */
267
+ function buildJailEnv(resultMarker: string): Record<string, string> {
268
+ return {
269
+ PATH: "/bin:/usr/bin:/usr/local/bin",
270
+ HOME: "/tmp",
271
+ LANG: "C.UTF-8",
272
+ MPLBACKEND: "Agg",
273
+ ATLAS_CHART_DIR: "/tmp/charts",
274
+ ATLAS_RESULT_MARKER: resultMarker,
275
+ };
276
+ }
277
+
278
+ /** Create a PythonBackend that executes code via nsjail. */
279
+ export function createPythonNsjailBackend(nsjailPath: string): PythonBackend {
280
+ return {
281
+ exec: async (code, data): Promise<PythonResult> => {
282
+ const execId = randomUUID();
283
+ const resultMarker = `__ATLAS_RESULT_${execId}__`;
284
+ const tmpDir = join("/tmp", `pyexec-${execId}`);
285
+ const codeFile = join(tmpDir, "user_code.py");
286
+ const wrapperFile = join(tmpDir, "wrapper.py");
287
+ const chartDir = join(tmpDir, "charts");
288
+
289
+ log.debug({ execId, codeLen: code.length }, "python nsjail execution starting");
290
+
291
+ try {
292
+ // Prepare tmpfs files
293
+ try {
294
+ mkdirSync(chartDir, { recursive: true });
295
+ writeFileSync(codeFile, code);
296
+ writeFileSync(wrapperFile, PYTHON_WRAPPER);
297
+ } catch (err) {
298
+ const detail = err instanceof Error ? err.message : String(err);
299
+ log.error({ err: detail, tmpDir, execId }, "Failed to prepare Python execution files");
300
+ return { success: false, error: `Infrastructure error preparing Python sandbox: ${detail}` };
301
+ }
302
+
303
+ const args = buildPythonNsjailArgs(nsjailPath, tmpDir, codeFile, wrapperFile, chartDir, resultMarker);
304
+ const env = buildJailEnv(resultMarker);
305
+ const stdinPayload = data ? JSON.stringify(data) : "";
306
+
307
+ let proc;
308
+ try {
309
+ proc = Bun.spawn(args, {
310
+ env,
311
+ stdin: "pipe",
312
+ stdout: "pipe",
313
+ stderr: "pipe",
314
+ });
315
+ } catch (err) {
316
+ const detail = err instanceof Error ? err.message : String(err);
317
+ log.error({ err: detail, execId }, "nsjail spawn failed for Python execution");
318
+ return { success: false, error: `nsjail infrastructure error: ${detail}` };
319
+ }
320
+
321
+ // Write data to stdin
322
+ try {
323
+ proc.stdin.write(stdinPayload);
324
+ proc.stdin.end();
325
+ } catch (err) {
326
+ const detail = err instanceof Error ? err.message : String(err);
327
+ log.warn({ err: detail, execId }, "stdin write error during Python execution");
328
+ if (data) {
329
+ proc.kill();
330
+ return { success: false, error: `Failed to inject data into Python sandbox: ${detail}` };
331
+ }
332
+ }
333
+
334
+ const [stdout, stderr] = await Promise.all([
335
+ readLimited(proc.stdout, MAX_OUTPUT),
336
+ readLimited(proc.stderr, MAX_OUTPUT),
337
+ ]);
338
+ const exitCode = await proc.exited;
339
+
340
+ log.debug({ execId, exitCode, stdoutLen: stdout.length }, "python nsjail execution finished");
341
+
342
+ // Extract structured result from the last marker line
343
+ const lines = stdout.split("\n");
344
+ const resultLine = lines.findLast((l) => l.startsWith(resultMarker));
345
+
346
+ if (resultLine) {
347
+ try {
348
+ return JSON.parse(resultLine.slice(resultMarker.length)) as PythonResult;
349
+ } catch {
350
+ log.warn({ execId, resultLine: resultLine.slice(0, 500) }, "failed to parse Python result JSON");
351
+ return {
352
+ success: false,
353
+ error: `Python produced unparseable output. stderr: ${stderr.trim().slice(0, 500)}`,
354
+ };
355
+ }
356
+ }
357
+
358
+ // No structured result — process errored before the wrapper could emit one
359
+ if (stdout.length >= MAX_OUTPUT) {
360
+ return {
361
+ success: false,
362
+ error: "Python output exceeded 1 MB limit — the result was likely truncated. " +
363
+ "Reduce print() output or use _atlas_table for large results.",
364
+ };
365
+ }
366
+
367
+ if (exitCode > 128) {
368
+ const signal = exitCode - 128;
369
+ const signalNames: Record<number, string> = { 6: "SIGABRT", 9: "SIGKILL", 11: "SIGSEGV", 15: "SIGTERM" };
370
+ const name = signalNames[signal] ?? `signal ${signal}`;
371
+ log.warn({ execId, signal, name }, "Python process killed by signal");
372
+ if (signal === 9) {
373
+ return { success: false, error: "Python execution killed (likely exceeded time or memory limit)" };
374
+ }
375
+ return {
376
+ success: false,
377
+ error: `Python process terminated by ${name}${stderr.trim() ? `: ${stderr.trim().slice(0, 500)}` : ""}`,
378
+ };
379
+ }
380
+
381
+ return {
382
+ success: false,
383
+ error: stderr.trim() || `Python execution failed (exit code ${exitCode})`,
384
+ };
385
+ } finally {
386
+ // Cleanup tmpfs
387
+ try {
388
+ rmSync(tmpDir, { recursive: true, force: true });
389
+ } catch (err) {
390
+ const detail = err instanceof Error ? err.message : String(err);
391
+ log.warn({ err: detail, tmpDir }, "failed to clean up Python tmpdir");
392
+ }
393
+ }
394
+ },
395
+ };
396
+ }