@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
@@ -31,9 +31,29 @@ const log = createLogger("sql");
31
31
 
32
32
  const parser = new Parser();
33
33
 
34
+ /**
35
+ * Strip SQL comments for regex guard testing.
36
+ *
37
+ * Block comments and line comments are removed so that anchored patterns
38
+ * like `^\s*(KILL)\b` cannot be bypassed with a leading comment.
39
+ *
40
+ * Only used for regex testing — the original SQL is passed to the AST parser
41
+ * unchanged so that comment-aware parsing still works correctly.
42
+ */
43
+ function stripSqlComments(sql: string): string {
44
+ // Single regex handles string literals, block comments, line comments,
45
+ // and MySQL-style # comments in one pass. String literals (single-quoted
46
+ // with '' escape) are preserved unchanged; comments are replaced with a space.
47
+ return sql
48
+ .replace(/'(?:[^']|'')*'|\/\*[\s\S]*?\*\/|--[^\n]*|#[^\n]*/g, (match) =>
49
+ match.startsWith("'") ? match : " ",
50
+ )
51
+ .trim();
52
+ }
53
+
34
54
  const FORBIDDEN_PATTERNS = [
35
55
  /\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b/i,
36
- /\b(GRANT|REVOKE|EXEC|EXECUTE|CALL)\b/i,
56
+ /\b(GRANT|REVOKE|EXEC|EXECUTE|CALL|KILL)\b/i,
37
57
  /\b(COPY|LOAD|VACUUM|REINDEX|OPTIMIZE)\b/i,
38
58
  /\bINTO\s+OUTFILE\b/i,
39
59
  ];
@@ -45,82 +65,68 @@ const MYSQL_FORBIDDEN_PATTERNS = [
45
65
  /\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
46
66
  ];
47
67
 
48
- // ClickHouse-specific patterns — admin/mutation commands unique to ClickHouse
49
- const CLICKHOUSE_FORBIDDEN_PATTERNS = [
50
- /\b(SYSTEM)\b/i,
51
- /\b(KILL)\b/i,
52
- /\b(ATTACH|DETACH)\b/i,
53
- /\b(RENAME)\b/i,
54
- /\b(EXCHANGE)\b/i,
55
- /\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
56
- ];
57
-
58
- // Snowflake-specific patterns — only applied when dbType === "snowflake"
59
- // Stage operations (PUT/GET/LIST/REMOVE/RM) and MERGE are Snowflake DML
60
- // not covered by the base patterns.
61
- // Stage ops are anchored to statement start (^\s*) to avoid false positives
62
- // on common words in data values (e.g., WHERE name = 'Get Ready').
63
- // MERGE/SHOW/DESCRIBE/EXPLAIN/USE use word-boundary since they rarely
64
- // appear as data values.
65
- const SNOWFLAKE_FORBIDDEN_PATTERNS = [
66
- /^\s*(PUT|GET|LIST|REMOVE|RM)\b/i,
67
- /\b(MERGE)\b/i,
68
- /\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
69
- ];
70
-
71
- // DuckDB-specific patterns — block PRAGMA, ATTACH, DETACH, INSTALL,
72
- // EXPORT, IMPORT, CHECKPOINT, file-reading functions, and SET.
73
- // Note: LOAD is already blocked by base FORBIDDEN_PATTERNS.
74
- const DUCKDB_FORBIDDEN_PATTERNS = [
75
- /\b(PRAGMA)\b/i,
76
- /\b(ATTACH|DETACH)\b/i,
77
- /\b(INSTALL)\b/i,
78
- /\b(EXPORT|IMPORT)\b/i,
79
- /\b(CHECKPOINT)\b/i,
80
- /\b(DESCRIBE|EXPLAIN|SHOW)\b/i,
81
- // Block file-reading table functions that can access the host filesystem
82
- /\b(read_csv_auto|read_csv|read_parquet|read_json|read_json_auto|read_text)\b/i,
83
- /\b(parquet_scan|csv_scan|json_scan)\b/i,
84
- // Block SET for configuration variables (DuckDB has no session-level read-only guard for :memory:)
85
- /^\s*SET\b/i,
86
- ];
87
-
88
68
  /**
89
69
  * Map DBType to node-sql-parser dialect string.
90
- * ClickHouse and DuckDB use PostgreSQL mode (closest match for SELECT syntax).
91
- * Snowflake uses "Snowflake" mode (alpha in node-sql-parser v5+).
70
+ *
71
+ * When a connectionId is provided, plugin-registered metadata is checked first.
72
+ * Falls back to the hardcoded switch for known types, and defaults to
73
+ * "PostgresQL" for unknown/custom dbType strings (plugin escape hatch).
92
74
  */
93
- export function parserDatabase(dbType: DBType): string {
75
+ export function parserDatabase(dbType: DBType | string, connectionId?: string): string {
76
+ // 1. Plugin metadata takes precedence
77
+ if (connectionId) {
78
+ const pluginDialect = connections.getParserDialect(connectionId);
79
+ if (pluginDialect) return pluginDialect;
80
+ }
81
+
82
+ // 2. Core types + fallback for plugin-registered types
94
83
  switch (dbType) {
95
84
  case "postgres": return "PostgresQL";
96
85
  case "mysql": return "MySQL";
97
- // node-sql-parser v5 has no ClickHouse dialect. PostgreSQL mode is the
98
- // closest match for ClickHouse SELECT syntax (CTEs, JOINs, subqueries).
99
- case "clickhouse": return "PostgresQL";
100
- case "snowflake": return "Snowflake";
101
- // DuckDB's SQL dialect is PostgreSQL-compatible. PostgreSQL mode handles
102
- // CTEs, JOINs, subqueries, window functions, and standard SQL syntax.
103
- case "duckdb": return "PostgresQL";
104
- case "salesforce":
105
- throw new Error(
106
- "Salesforce uses SOQL, not SQL. Use the querySalesforce tool instead of executeSQL.",
86
+ default:
87
+ // Unknown types (plugin-registered via `string & {}`) default to
88
+ // PostgreSQL mode as a safe fallback. Log a warning so plugin authors
89
+ // know to register a parserDialect via ConnectionPluginMeta.
90
+ log.warn(
91
+ { dbType, connectionId },
92
+ "No parser dialect registered for dbType '%s' — falling back to PostgreSQL parser. " +
93
+ "Register a parserDialect via ConnectionPluginMeta to use the correct SQL grammar.",
94
+ dbType,
107
95
  );
108
- default: {
109
- const _exhaustive: never = dbType;
110
- throw new Error(`Unknown database type: ${_exhaustive}`);
111
- }
96
+ return "PostgresQL";
112
97
  }
113
98
  }
114
99
 
115
- function getExtraPatterns(dbType: DBType): RegExp[] {
100
+ /**
101
+ * Get extra forbidden patterns for a connection.
102
+ *
103
+ * When a connectionId is provided, plugin-registered patterns are checked first.
104
+ * Falls back to the hardcoded arrays for known types, and returns an empty
105
+ * array for unknown/custom dbType strings.
106
+ */
107
+ function getExtraPatterns(dbType: DBType | string, connectionId?: string): RegExp[] {
108
+ // 1. Plugin metadata takes precedence
109
+ if (connectionId) {
110
+ const pluginPatterns = connections.getForbiddenPatterns(connectionId);
111
+ if (pluginPatterns.length > 0) return pluginPatterns;
112
+ }
113
+
114
+ // 2. Core types + fallback for plugin-registered types
116
115
  switch (dbType) {
117
116
  case "postgres": return [];
118
117
  case "mysql": return MYSQL_FORBIDDEN_PATTERNS;
119
- case "clickhouse": return CLICKHOUSE_FORBIDDEN_PATTERNS;
120
- case "snowflake": return SNOWFLAKE_FORBIDDEN_PATTERNS;
121
- case "duckdb": return DUCKDB_FORBIDDEN_PATTERNS;
122
- case "salesforce": return []; // Salesforce is redirected before reaching here
123
- default: { const _exhaustive: never = dbType; throw new Error(`Unknown database type: ${_exhaustive}`); }
118
+ default:
119
+ // Unknown types (plugin-registered) — no extra patterns from core.
120
+ // Warn so plugin authors know to register forbiddenPatterns.
121
+ if (dbType) {
122
+ log.warn(
123
+ { dbType, connectionId },
124
+ "No forbidden patterns registered for dbType '%s' — only base DML/DDL patterns apply. " +
125
+ "Register forbiddenPatterns via ConnectionPluginMeta for database-specific protection.",
126
+ dbType,
127
+ );
128
+ }
129
+ return [];
124
130
  }
125
131
  }
126
132
 
@@ -128,29 +134,23 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
128
134
  // Resolve DB type for this connection.
129
135
  // When an explicit connectionId is given but not found, return a validation
130
136
  // error instead of silently falling back — wrong parser mode is a security risk.
131
- let dbType: DBType;
137
+ let dbType: DBType | string;
132
138
  if (connectionId) {
133
139
  try {
134
140
  dbType = connections.getDBType(connectionId);
135
- } catch {
141
+ } catch (err) {
142
+ log.debug({ err, connectionId }, "getDBType failed for connectionId");
136
143
  return { valid: false, error: `Connection "${connectionId}" is not registered.` };
137
144
  }
138
145
  } else {
139
146
  try {
140
147
  dbType = detectDBType();
141
- } catch {
142
- return { valid: false, error: "No valid datasource configured. Set ATLAS_DATASOURCE_URL to a PostgreSQL, MySQL, ClickHouse, Snowflake, DuckDB, or Salesforce connection string." };
148
+ } catch (err) {
149
+ log.debug({ err }, "detectDBType failed — no valid datasource configured");
150
+ return { valid: false, error: "No valid datasource configured. Set ATLAS_DATASOURCE_URL to a PostgreSQL or MySQL connection string, or register a datasource plugin." };
143
151
  }
144
152
  }
145
153
 
146
- // Salesforce uses SOQL — redirect to the querySalesforce tool
147
- if (dbType === "salesforce") {
148
- return {
149
- valid: false,
150
- error: "This connection uses Salesforce (SOQL). Use the querySalesforce tool instead of executeSQL.",
151
- };
152
- }
153
-
154
154
  // 0. Reject empty / whitespace-only input
155
155
  const trimmed = sql.trim().replace(/;\s*$/, "");
156
156
  if (!trimmed) {
@@ -158,10 +158,14 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
158
158
  }
159
159
 
160
160
  // 1. Regex guard against mutation keywords
161
- const extraPatterns = getExtraPatterns(dbType);
161
+ //
162
+ // Strip comments before testing so that leading block/line comments
163
+ // cannot bypass start-of-string anchored patterns (e.g. `/* x */ KILL ...`).
164
+ const forRegex = stripSqlComments(trimmed);
165
+ const extraPatterns = getExtraPatterns(dbType, connectionId);
162
166
  const patterns = [...FORBIDDEN_PATTERNS, ...extraPatterns];
163
167
  for (const pattern of patterns) {
164
- if (pattern.test(trimmed)) {
168
+ if (pattern.test(forRegex)) {
165
169
  return {
166
170
  valid: false,
167
171
  error: `Forbidden SQL operation detected: ${pattern.source}`,
@@ -177,7 +181,7 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
177
181
  // attempt. The agent can always reformulate into standard SQL that parses.
178
182
  const cteNames = new Set<string>();
179
183
  try {
180
- const ast = parser.astify(trimmed, { database: parserDatabase(dbType) });
184
+ const ast = parser.astify(trimmed, { database: parserDatabase(dbType, connectionId) });
181
185
  const statements = Array.isArray(ast) ? ast : [ast];
182
186
 
183
187
  // Single-statement check — reject batched queries
@@ -192,7 +196,8 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
192
196
  error: `Only SELECT statements are allowed, got: ${stmt.type}`,
193
197
  };
194
198
  }
195
- // Collect CTE names so the table whitelist can ignore them
199
+ // CTE extraction: node-sql-parser doesn't expose .with in its type definitions,
200
+ // but the property exists at runtime for SELECT statements with CTEs.
196
201
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
202
  if (Array.isArray((stmt as any).with)) {
198
203
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -204,21 +209,16 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
204
209
  }
205
210
  } catch (err) {
206
211
  const detail = err instanceof Error ? err.message : "";
207
- const hint = dbType === "clickhouse"
208
- ? " Note: ClickHouse-specific syntax (PREWHERE, LIMIT BY, arrayJoin) is not supported by the SQL validator. Use standard SQL equivalents."
209
- : dbType === "duckdb"
210
- ? " Note: DuckDB-specific syntax (QUALIFY, EXCLUDE, REPLACE, STRUCT literals) may not be supported by the SQL validator. Use standard SQL equivalents."
211
- : "";
212
212
  return {
213
213
  valid: false,
214
- error: `Query could not be parsed.${detail ? ` ${detail}.` : ""} Rewrite using standard SQL syntax.${hint}`,
214
+ error: `Query could not be parsed.${detail ? ` ${detail}.` : ""} Rewrite using standard SQL syntax.`,
215
215
  };
216
216
  }
217
217
 
218
218
  // 3. Table whitelist check
219
219
  if (process.env.ATLAS_TABLE_WHITELIST !== "false") {
220
220
  try {
221
- const tables = parser.tableList(trimmed, { database: parserDatabase(dbType) });
221
+ const tables = parser.tableList(trimmed, { database: parserDatabase(dbType, connectionId) });
222
222
  const allowed = getWhitelistedTables(connectionId);
223
223
 
224
224
  for (const ref of tables) {
@@ -515,7 +515,7 @@ Rules:
515
515
  // Extract tables from the (possibly plugin-mutated) SQL
516
516
  let queriedTables: Set<string>;
517
517
  try {
518
- const dialect = parserDatabase(dbType);
518
+ const dialect = parserDatabase(dbType, connId);
519
519
  const tableRefs = parser.tableList(normalizedMutated, { database: dialect });
520
520
  queriedTables = new Set(
521
521
  tableRefs
@@ -39,6 +39,12 @@ declare module "@vercel/sandbox" {
39
39
  stderr(): Promise<string>;
40
40
  }
41
41
 
42
+ /** Network policy update — replaces the current firewall configuration. */
43
+ type NetworkPolicyUpdate =
44
+ | "deny-all"
45
+ | "allow-all"
46
+ | { allow?: string[] | Record<string, unknown>; subnets?: { allow?: string[]; deny?: string[] } };
47
+
42
48
  class Sandbox {
43
49
  static create(opts?: SandboxCreateOptions): Promise<Sandbox>;
44
50
  mkDir(path: string): Promise<void>;
@@ -49,6 +55,7 @@ declare module "@vercel/sandbox" {
49
55
  args?: string[],
50
56
  opts?: { signal?: AbortSignal }
51
57
  ): Promise<CommandFinished>;
58
+ updateNetworkPolicy(policy: NetworkPolicyUpdate): Promise<void>;
52
59
  stop(): Promise<Sandbox>;
53
60
  }
54
61
  }
@@ -1,26 +1,95 @@
1
1
  "use client";
2
2
 
3
+ import { useState, useEffect } from "react";
3
4
  import type { ReactNode } from "react";
4
5
  import { SidebarProvider, SidebarInset, SidebarTrigger } from "@/components/ui/sidebar";
5
6
  import { Separator } from "@/components/ui/separator";
6
7
  import { AdminSidebar } from "./admin-sidebar";
7
-
8
- // TODO: Add real role gating once admin API enforces auth.
9
- // For now, all users can access the admin console — the admin API
10
- // endpoints will reject unauthorized requests server-side.
8
+ import { useAtlasConfig } from "@/ui/context";
9
+ import { ManagedAuthCard } from "@/ui/components/chat/managed-auth-card";
10
+ import { LoadingState } from "./loading-state";
11
+ import { ChangePasswordDialog } from "./change-password-dialog";
11
12
 
12
13
  export function AdminLayout({ children }: { children: ReactNode }) {
14
+ const { authClient, apiUrl, isCrossOrigin } = useAtlasConfig();
15
+ const session = authClient.useSession();
16
+ const [passwordChangeRequired, setPasswordChangeRequired] = useState(false);
17
+
18
+ const credentials: RequestCredentials = isCrossOrigin ? "include" : "same-origin";
19
+
20
+ // Check if password change is required after session loads
21
+ useEffect(() => {
22
+ if (!session.data?.user) return;
23
+
24
+ async function checkPasswordStatus() {
25
+ try {
26
+ const res = await fetch(`${apiUrl}/api/v1/admin/me/password-status`, { credentials });
27
+ if (!res.ok) return;
28
+ const data = await res.json();
29
+ if (data.passwordChangeRequired) setPasswordChangeRequired(true);
30
+ } catch {
31
+ // Non-critical — skip silently
32
+ }
33
+ }
34
+ checkPasswordStatus();
35
+ }, [session.data?.user, apiUrl, credentials]);
36
+
37
+ // Loading session
38
+ if (session.isPending) {
39
+ return (
40
+ <div className="flex h-dvh items-center justify-center">
41
+ <LoadingState message="Checking authentication..." />
42
+ </div>
43
+ );
44
+ }
45
+
46
+ // Not signed in
47
+ if (!session.data?.user) {
48
+ return <ManagedAuthCard />;
49
+ }
50
+
51
+ // Signed in but not admin
52
+ const role = (session.data.user as Record<string, unknown>).role;
53
+ if (role !== "admin") {
54
+ return (
55
+ <div className="flex h-dvh items-center justify-center">
56
+ <div className="w-full max-w-sm space-y-3 rounded-lg border border-zinc-200 bg-zinc-50 p-6 text-center dark:border-zinc-700 dark:bg-zinc-900">
57
+ <h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
58
+ Access Denied
59
+ </h2>
60
+ <p className="text-sm text-zinc-500 dark:text-zinc-400">
61
+ The admin console requires the <strong>admin</strong> role. You are signed in
62
+ as <strong>{session.data.user.email}</strong> with role <strong>{String(role ?? "viewer")}</strong>.
63
+ </p>
64
+ <button
65
+ onClick={() => authClient.signOut()}
66
+ className="mt-2 rounded-lg bg-zinc-200 px-4 py-2 text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-300 dark:bg-zinc-700 dark:text-zinc-100 dark:hover:bg-zinc-600"
67
+ >
68
+ Sign out
69
+ </button>
70
+ </div>
71
+ </div>
72
+ );
73
+ }
74
+
13
75
  return (
14
76
  <SidebarProvider>
15
77
  <AdminSidebar />
16
78
  <SidebarInset>
17
- <header className="flex h-12 shrink-0 items-center gap-2 border-b px-4">
18
- <SidebarTrigger className="-ml-1" />
19
- <Separator orientation="vertical" className="mr-2 h-4" />
20
- <span className="text-sm font-medium text-muted-foreground">Admin Console</span>
79
+ <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
80
+ <div className="flex items-center gap-2 px-4">
81
+ <SidebarTrigger className="-ml-1" />
82
+ <Separator orientation="vertical" className="mr-2 h-4" />
83
+ <span className="text-sm font-medium text-muted-foreground">Admin Console</span>
84
+ </div>
21
85
  </header>
22
86
  <div className="flex-1 overflow-auto">{children}</div>
23
87
  </SidebarInset>
88
+
89
+ <ChangePasswordDialog
90
+ open={passwordChangeRequired}
91
+ onComplete={() => setPasswordChangeRequired(false)}
92
+ />
24
93
  </SidebarProvider>
25
94
  );
26
95
  }
@@ -7,6 +7,7 @@ import {
7
7
  Database,
8
8
  Cable,
9
9
  ScrollText,
10
+ Users,
10
11
  Puzzle,
11
12
  CalendarClock,
12
13
  Zap,
@@ -23,7 +24,7 @@ import {
23
24
  SidebarMenuItem,
24
25
  SidebarMenuButton,
25
26
  SidebarFooter,
26
- SidebarSeparator,
27
+ SidebarRail,
27
28
  } from "@/components/ui/sidebar";
28
29
 
29
30
  const navItems = [
@@ -31,6 +32,7 @@ const navItems = [
31
32
  { href: "/admin/semantic", label: "Semantic Layer", icon: Database },
32
33
  { href: "/admin/connections", label: "Connections", icon: Cable },
33
34
  { href: "/admin/audit", label: "Audit", icon: ScrollText },
35
+ { href: "/admin/users", label: "Users", icon: Users },
34
36
  { href: "/admin/plugins", label: "Plugins", icon: Puzzle },
35
37
  { href: "/admin/scheduled-tasks", label: "Scheduled Tasks", icon: CalendarClock },
36
38
  { href: "/admin/actions", label: "Actions", icon: Zap },
@@ -45,20 +47,26 @@ export function AdminSidebar() {
45
47
  }
46
48
 
47
49
  return (
48
- <Sidebar>
49
- <SidebarHeader className="px-3 pt-4 pb-2">
50
- <div className="flex items-center gap-2.5">
51
- <svg viewBox="0 0 256 256" fill="none" className="size-6 shrink-0" aria-hidden="true">
52
- <path d="M128 24 L232 208 L24 208 Z" stroke="#23CE9E" strokeWidth="14" fill="none" strokeLinejoin="round" />
53
- <circle cx="128" cy="28" r="16" fill="#23CE9E" />
54
- </svg>
55
- <div>
56
- <p className="text-sm font-semibold leading-none tracking-tight">Atlas</p>
57
- <p className="text-xs text-muted-foreground">Admin Console</p>
58
- </div>
59
- </div>
50
+ <Sidebar collapsible="icon">
51
+ <SidebarHeader>
52
+ <SidebarMenu>
53
+ <SidebarMenuItem>
54
+ <SidebarMenuButton size="lg" asChild>
55
+ <Link href="/admin">
56
+ <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
57
+ <svg viewBox="0 0 256 256" fill="none" className="size-4" aria-hidden="true">
58
+ <path d="M128 24 L232 208 L24 208 Z" stroke="currentColor" strokeWidth="20" fill="none" strokeLinejoin="round" />
59
+ </svg>
60
+ </div>
61
+ <div className="grid flex-1 text-left text-sm leading-tight">
62
+ <span className="truncate font-semibold">Atlas</span>
63
+ <span className="truncate text-xs">Admin Console</span>
64
+ </div>
65
+ </Link>
66
+ </SidebarMenuButton>
67
+ </SidebarMenuItem>
68
+ </SidebarMenu>
60
69
  </SidebarHeader>
61
- <SidebarSeparator />
62
70
  <SidebarContent>
63
71
  <SidebarGroup>
64
72
  <SidebarGroupLabel>Navigation</SidebarGroupLabel>
@@ -68,7 +76,7 @@ export function AdminSidebar() {
68
76
  <SidebarMenuItem key={item.href}>
69
77
  <SidebarMenuButton asChild isActive={isActive(item)} tooltip={item.label}>
70
78
  <Link href={item.href}>
71
- <item.icon className="size-4" />
79
+ <item.icon />
72
80
  <span>{item.label}</span>
73
81
  </Link>
74
82
  </SidebarMenuButton>
@@ -79,18 +87,18 @@ export function AdminSidebar() {
79
87
  </SidebarGroup>
80
88
  </SidebarContent>
81
89
  <SidebarFooter>
82
- <SidebarSeparator />
83
90
  <SidebarMenu>
84
91
  <SidebarMenuItem>
85
92
  <SidebarMenuButton asChild tooltip="Back to Chat">
86
93
  <Link href="/">
87
- <ArrowLeft className="size-4" />
94
+ <ArrowLeft />
88
95
  <span>Back to Chat</span>
89
96
  </Link>
90
97
  </SidebarMenuButton>
91
98
  </SidebarMenuItem>
92
99
  </SidebarMenu>
93
100
  </SidebarFooter>
101
+ <SidebarRail />
94
102
  </Sidebar>
95
103
  );
96
104
  }
@@ -0,0 +1,128 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogContent,
7
+ AlertDialogDescription,
8
+ AlertDialogFooter,
9
+ AlertDialogHeader,
10
+ AlertDialogTitle,
11
+ } from "@/components/ui/alert-dialog";
12
+ import { Button } from "@/components/ui/button";
13
+ import { Input } from "@/components/ui/input";
14
+ import { useAtlasConfig } from "@/ui/context";
15
+
16
+ export function ChangePasswordDialog({
17
+ open,
18
+ onComplete,
19
+ }: {
20
+ open: boolean;
21
+ onComplete: () => void;
22
+ }) {
23
+ const { apiUrl, isCrossOrigin } = useAtlasConfig();
24
+ const credentials: RequestCredentials = isCrossOrigin ? "include" : "same-origin";
25
+
26
+ const [currentPassword, setCurrentPassword] = useState("atlas-dev");
27
+ const [newPassword, setNewPassword] = useState("");
28
+ const [confirmPassword, setConfirmPassword] = useState("");
29
+ const [error, setError] = useState("");
30
+ const [loading, setLoading] = useState(false);
31
+
32
+ async function handleSubmit(e: React.FormEvent) {
33
+ e.preventDefault();
34
+ setError("");
35
+
36
+ if (newPassword.length < 8) {
37
+ setError("Password must be at least 8 characters.");
38
+ return;
39
+ }
40
+ if (newPassword !== confirmPassword) {
41
+ setError("Passwords do not match.");
42
+ return;
43
+ }
44
+ if (newPassword === currentPassword) {
45
+ setError("New password must be different from current password.");
46
+ return;
47
+ }
48
+
49
+ setLoading(true);
50
+ try {
51
+ const res = await fetch(`${apiUrl}/api/v1/admin/me/password`, {
52
+ method: "POST",
53
+ credentials,
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify({ currentPassword, newPassword }),
56
+ });
57
+
58
+ if (!res.ok) {
59
+ const data = await res.json().catch(() => ({}));
60
+ setError(data.message ?? `Failed (HTTP ${res.status})`);
61
+ return;
62
+ }
63
+
64
+ onComplete();
65
+ } catch (err) {
66
+ setError(err instanceof Error ? err.message : "Failed to change password");
67
+ } finally {
68
+ setLoading(false);
69
+ }
70
+ }
71
+
72
+ return (
73
+ <AlertDialog open={open}>
74
+ <AlertDialogContent className="sm:max-w-md" onEscapeKeyDown={(e) => e.preventDefault()}>
75
+ <form onSubmit={handleSubmit}>
76
+ <AlertDialogHeader>
77
+ <AlertDialogTitle>Change your password</AlertDialogTitle>
78
+ <AlertDialogDescription>
79
+ You&apos;re using the default dev password. Please set a new password to continue.
80
+ </AlertDialogDescription>
81
+ </AlertDialogHeader>
82
+
83
+ <div className="space-y-3 py-4">
84
+ <div className="space-y-1">
85
+ <label className="text-xs font-medium text-muted-foreground">Current password</label>
86
+ <Input
87
+ type="password"
88
+ value={currentPassword}
89
+ onChange={(e) => setCurrentPassword(e.target.value)}
90
+ required
91
+ />
92
+ </div>
93
+ <div className="space-y-1">
94
+ <label className="text-xs font-medium text-muted-foreground">New password</label>
95
+ <Input
96
+ type="password"
97
+ value={newPassword}
98
+ onChange={(e) => setNewPassword(e.target.value)}
99
+ required
100
+ minLength={8}
101
+ placeholder="At least 8 characters"
102
+ />
103
+ </div>
104
+ <div className="space-y-1">
105
+ <label className="text-xs font-medium text-muted-foreground">Confirm new password</label>
106
+ <Input
107
+ type="password"
108
+ value={confirmPassword}
109
+ onChange={(e) => setConfirmPassword(e.target.value)}
110
+ required
111
+ minLength={8}
112
+ />
113
+ </div>
114
+ {error && (
115
+ <p className="text-xs text-red-600 dark:text-red-400">{error}</p>
116
+ )}
117
+ </div>
118
+
119
+ <AlertDialogFooter>
120
+ <Button type="submit" disabled={loading}>
121
+ {loading ? "Changing..." : "Change password"}
122
+ </Button>
123
+ </AlertDialogFooter>
124
+ </form>
125
+ </AlertDialogContent>
126
+ </AlertDialog>
127
+ );
128
+ }
@@ -62,7 +62,7 @@ function normalizeList<T>(
62
62
  keyName: string,
63
63
  ): (T & Record<string, unknown>)[] {
64
64
  if (!data) return [];
65
- if (Array.isArray(data)) return data;
65
+ if (Array.isArray(data)) return data as (T & Record<string, unknown>)[];
66
66
  return Object.entries(data).map(([key, value]) => ({ ...value, [keyName]: key }));
67
67
  }
68
68
 
@@ -211,8 +211,8 @@ export function EntityDetail({ entity }: { entity: EntityData }) {
211
211
  <section>
212
212
  <h3 className="mb-3 text-sm font-semibold">Query Patterns ({patterns.length})</h3>
213
213
  <div className="space-y-3">
214
- {patterns.map((p) => (
215
- <Card key={p.name} className="shadow-none">
214
+ {patterns.map((p, i) => (
215
+ <Card key={`${p.name}-${i}`} className="shadow-none">
216
216
  <CardHeader className="py-3 pb-2">
217
217
  <CardTitle className="text-sm">{p.name}</CardTitle>
218
218
  <p className="text-xs text-muted-foreground">{p.description}</p>