@useatlas/create 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -18
- package/index.ts +191 -31
- package/package.json +1 -1
- package/templates/docker/.env.example +3 -3
- package/templates/docker/Dockerfile.sidecar +28 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +11 -11
- package/templates/docker/bin/atlas.ts +120 -56
- package/templates/docker/data/demo-semantic/catalog.yml +51 -27
- package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
- package/templates/docker/data/demo-semantic/glossary.yml +104 -8
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/docker/docker-compose.yml +1 -1
- package/templates/docker/docs/deploy.md +4 -41
- package/templates/docker/package.json +17 -1
- package/templates/docker/semantic/catalog.yml +62 -3
- package/templates/docker/semantic/entities/accounts.yml +162 -0
- package/templates/docker/semantic/entities/companies.yml +143 -0
- package/templates/docker/semantic/entities/people.yml +132 -0
- package/templates/docker/semantic/glossary.yml +116 -4
- package/templates/docker/semantic/metrics/accounts.yml +77 -0
- package/templates/docker/semantic/metrics/companies.yml +63 -0
- package/templates/docker/sidecar/Dockerfile +5 -6
- package/templates/docker/sidecar/railway.json +1 -2
- package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/docker/src/api/__tests__/health.test.ts +30 -8
- package/templates/docker/src/api/routes/admin.ts +549 -8
- package/templates/docker/src/api/routes/chat.ts +5 -20
- package/templates/docker/src/api/routes/health.ts +39 -27
- package/templates/docker/src/api/routes/openapi.ts +1329 -74
- package/templates/docker/src/api/routes/query.ts +2 -1
- package/templates/docker/src/api/server.ts +27 -0
- package/templates/docker/src/app/api/[...route]/route.ts +2 -2
- package/templates/docker/src/app/globals.css +13 -12
- package/templates/docker/src/app/layout.tsx +9 -2
- package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/docker/src/components/ui/badge.tsx +48 -0
- package/templates/docker/src/components/ui/button.tsx +64 -0
- package/templates/docker/src/components/ui/card.tsx +92 -0
- package/templates/docker/src/components/ui/collapsible.tsx +33 -0
- package/templates/docker/src/components/ui/command.tsx +184 -0
- package/templates/docker/src/components/ui/dialog.tsx +158 -0
- package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/docker/src/components/ui/input.tsx +21 -0
- package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
- package/templates/docker/src/components/ui/select.tsx +190 -0
- package/templates/docker/src/components/ui/separator.tsx +28 -0
- package/templates/docker/src/components/ui/sheet.tsx +143 -0
- package/templates/docker/src/components/ui/sidebar.tsx +726 -0
- package/templates/docker/src/components/ui/skeleton.tsx +13 -0
- package/templates/docker/src/components/ui/table.tsx +116 -0
- package/templates/docker/src/components/ui/tabs.tsx +91 -0
- package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
- package/templates/docker/src/components/ui/toggle.tsx +47 -0
- package/templates/docker/src/components/ui/tooltip.tsx +57 -0
- package/templates/docker/src/hooks/use-mobile.ts +19 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
- package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/docker/src/lib/agent-query.ts +5 -23
- package/templates/docker/src/lib/agent.ts +32 -112
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/docker/src/lib/auth/middleware.ts +30 -4
- package/templates/docker/src/lib/auth/migrate.ts +97 -0
- package/templates/docker/src/lib/auth/server.ts +12 -1
- package/templates/docker/src/lib/config.ts +38 -40
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/docker/src/lib/db/connection.ts +87 -265
- package/templates/docker/src/lib/db/internal.ts +6 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/docker/src/lib/plugins/index.ts +4 -1
- package/templates/docker/src/lib/plugins/migrate.ts +104 -1
- package/templates/docker/src/lib/plugins/registry.ts +14 -8
- package/templates/docker/src/lib/plugins/wiring.ts +113 -4
- package/templates/docker/src/lib/providers.ts +6 -1
- package/templates/docker/src/lib/security.ts +24 -0
- package/templates/docker/src/lib/semantic.ts +2 -0
- package/templates/docker/src/lib/sidecar-types.ts +12 -1
- package/templates/docker/src/lib/startup.ts +71 -101
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/docker/src/lib/tools/explore.ts +28 -32
- package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/docker/src/lib/tools/python.ts +367 -0
- package/templates/docker/src/lib/tools/registry.ts +49 -22
- package/templates/docker/src/lib/tools/sql.ts +88 -88
- package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/docker/src/ui/context.tsx +1 -1
- package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/docker/tsconfig.json +2 -2
- package/templates/nextjs-standalone/.env.example +1 -1
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +11 -11
- package/templates/nextjs-standalone/bin/atlas.ts +120 -56
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/nextjs-standalone/docs/deploy.md +4 -41
- package/templates/nextjs-standalone/package.json +11 -2
- package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
- package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
- package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
- package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
- package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
- package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
- package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
- package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
- package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
- package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
- package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
- package/templates/nextjs-standalone/src/api/server.ts +27 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
- package/templates/nextjs-standalone/src/app/globals.css +13 -12
- package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
- package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
- package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
- package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
- package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
- package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
- package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
- package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
- package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
- package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
- package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
- package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
- package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
- package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
- package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
- package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
- package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
- package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
- package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
- package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
- package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
- package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
- package/templates/nextjs-standalone/src/lib/config.ts +38 -40
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
- package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +104 -1
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +14 -8
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
- package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
- package/templates/nextjs-standalone/src/lib/security.ts +24 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
- package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
- package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/nextjs-standalone/tsconfig.json +0 -1
- package/templates/nextjs-standalone/vercel.json +4 -1
- package/templates/docker/render.yaml +0 -34
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/docker/src/lib/db/duckdb.ts +0 -122
- package/templates/docker/src/lib/db/salesforce.ts +0 -342
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/docker/src/lib/tools/salesforce.ts +0 -138
- package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
|
@@ -84,84 +84,15 @@ This database uses MySQL. Key differences from PostgreSQL:
|
|
|
84
84
|
- \`LIMIT offset, count\` or \`LIMIT count OFFSET offset\` — both forms work
|
|
85
85
|
- \`COALESCE\`, \`CASE\`, \`NULLIF\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically`;
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- Use \`toString(col)\` for explicit string casts — no \`::text\` casting
|
|
94
|
-
- Use \`toInt32(col)\`, \`toFloat64(col)\` for numeric casts — no \`::integer\` casting
|
|
95
|
-
- Use \`ifNull(col, default)\` or \`COALESCE(col, default)\` — both work
|
|
96
|
-
- \`arrayJoin(arr)\` unnests array columns into rows
|
|
97
|
-
- No \`UPDATE\` or \`DELETE\` — ClickHouse is append-only OLAP
|
|
98
|
-
- No foreign keys — joins are supported but there are no referential integrity constraints
|
|
99
|
-
- \`count()\` instead of \`COUNT(*)\` — both work but \`count()\` is idiomatic
|
|
100
|
-
- Use single quotes for strings, double quotes or backticks for identifiers
|
|
101
|
-
- \`argMax(col, ordering_col)\` / \`argMin\` for "latest value" queries
|
|
102
|
-
- Date arithmetic: \`today()\`, \`yesterday()\`, \`now()\`, \`toStartOfMonth(col)\`, \`toStartOfWeek(col)\`
|
|
103
|
-
- \`COALESCE\`, \`CASE\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically
|
|
104
|
-
- Note: Atlas SQL validation uses PostgreSQL-compatible parsing. Avoid ClickHouse-only syntax (PREWHERE, LIMIT BY, WITH TOTALS) — use standard SQL equivalents instead
|
|
105
|
-
- \`EXPLAIN\`, \`SHOW\`, and \`DESCRIBE\` are not available — use the explore tool to read entity schema files instead`;
|
|
106
|
-
|
|
107
|
-
const SOQL_DIALECT_GUIDE = `
|
|
108
|
-
|
|
109
|
-
## Query Language: Salesforce SOQL
|
|
110
|
-
This datasource uses Salesforce Object Query Language (SOQL). Key differences from SQL:
|
|
111
|
-
- **No JOINs** — use relationship queries instead:
|
|
112
|
-
- Child-to-parent: \`SELECT Account.Name FROM Contact\`
|
|
113
|
-
- Parent-to-child: \`SELECT Name, (SELECT LastName FROM Contacts) FROM Account\`
|
|
114
|
-
- **Object names** instead of table names (e.g. \`Account\`, \`Contact\`, \`Opportunity\`)
|
|
115
|
-
- **Field API names** — always use the API name (e.g. \`FirstName\`), not the label
|
|
116
|
-
- **No wildcards** — \`SELECT *\` is not supported; list fields explicitly
|
|
117
|
-
- **Date literals**: \`TODAY\`, \`YESTERDAY\`, \`LAST_WEEK\`, \`THIS_MONTH\`, \`LAST_N_DAYS:30\`, \`NEXT_N_DAYS:7\`
|
|
118
|
-
- **Date functions**: \`CALENDAR_YEAR(CloseDate)\`, \`CALENDAR_MONTH(CloseDate)\`, \`DAY_IN_MONTH(CloseDate)\`
|
|
119
|
-
- **Aggregate functions**: \`COUNT()\`, \`COUNT(Id)\`, \`SUM(Amount)\`, \`AVG(Amount)\`, \`MIN(Amount)\`, \`MAX(Amount)\`
|
|
120
|
-
- **GROUP BY** works similarly to SQL: \`SELECT StageName, COUNT(Id) FROM Opportunity GROUP BY StageName\`
|
|
121
|
-
- **HAVING** clause is supported for filtering aggregates
|
|
122
|
-
- **LIMIT** and \`OFFSET\` are supported
|
|
123
|
-
- **No subqueries in FROM** — subqueries only in WHERE (semi-joins): \`WHERE AccountId IN (SELECT Id FROM Account WHERE ...)\`
|
|
124
|
-
- **No UNION, INTERSECT, EXCEPT**
|
|
125
|
-
- Use the \`querySalesforce\` tool (not \`executeSQL\`) for all Salesforce queries`;
|
|
126
|
-
|
|
127
|
-
const SNOWFLAKE_DIALECT_GUIDE = `
|
|
128
|
-
|
|
129
|
-
## SQL Dialect: Snowflake
|
|
130
|
-
This database uses Snowflake. Key differences from PostgreSQL:
|
|
131
|
-
- \`ILIKE\` works for case-insensitive matching (same as PostgreSQL)
|
|
132
|
-
- \`::type\` casting works (e.g. \`col::VARCHAR\`), plus \`TRY_CAST(col AS type)\` for safe casting that returns NULL on failure
|
|
133
|
-
- Identifiers are case-insensitive by default; double-quoted identifiers are case-sensitive
|
|
134
|
-
- Use \`FLATTEN()\` / \`LATERAL FLATTEN(input => col)\` to unnest semi-structured (VARIANT/ARRAY/OBJECT) data
|
|
135
|
-
- \`QUALIFY\` clause filters window function results directly (e.g. \`QUALIFY ROW_NUMBER() OVER (...) = 1\`)
|
|
136
|
-
- \`DATE_TRUNC('month', col)\` syntax (same as PostgreSQL)
|
|
137
|
-
- No \`LIMIT offset, count\` — use \`LIMIT count OFFSET offset\`
|
|
138
|
-
- Use \`LISTAGG(col, ', ')\` instead of \`STRING_AGG(col, ', ')\` or \`ARRAY_AGG(col)\` for aggregation
|
|
139
|
-
- \`COALESCE\`, \`CASE\`, \`NULLIF\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically`;
|
|
140
|
-
|
|
141
|
-
const DUCKDB_DIALECT_GUIDE = `
|
|
142
|
-
|
|
143
|
-
## SQL Dialect: DuckDB
|
|
144
|
-
This database uses DuckDB, an in-process analytical engine. DuckDB's SQL is PostgreSQL-compatible with extensions:
|
|
145
|
-
- \`::type\` casting works (e.g. \`col::INTEGER\`, \`col::VARCHAR\`) — same as PostgreSQL
|
|
146
|
-
- \`ILIKE\` works for case-insensitive matching (same as PostgreSQL)
|
|
147
|
-
- \`EXTRACT(YEAR FROM col)\`, \`DATE_TRUNC('month', col)\`, \`TO_CHAR(col, 'YYYY-MM')\` — same as PostgreSQL
|
|
148
|
-
- \`STRING_AGG(col, ', ')\` works (same as PostgreSQL)
|
|
149
|
-
- \`PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col)\` works for percentile calculations
|
|
150
|
-
- \`LIST_AGG\`, \`ARRAY_AGG\`, \`LIST()\` for array aggregation
|
|
151
|
-
- \`UNNEST(list_col)\` to expand list/array columns into rows
|
|
152
|
-
- Note: Atlas SQL validation uses PostgreSQL-compatible parsing. Avoid DuckDB-only syntax (QUALIFY, EXCLUDE, REPLACE, STRUCT_PACK, COLUMNS(*), COLUMNS(regex), STRUCT literals with curly braces) — use standard SQL equivalents instead
|
|
153
|
-
- \`COALESCE\`, \`CASE\`, \`NULLIF\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically`;
|
|
87
|
+
// Display names for core DB types. Plugin-registered types fall through
|
|
88
|
+
// to the capitalize fallback intentionally.
|
|
89
|
+
const DIALECT_DISPLAY_NAMES: Record<string, string> = {
|
|
90
|
+
postgres: "PostgreSQL",
|
|
91
|
+
mysql: "MySQL",
|
|
92
|
+
};
|
|
154
93
|
|
|
155
94
|
function dialectName(dbType: DBType): string {
|
|
156
|
-
|
|
157
|
-
case "postgres": return "PostgreSQL";
|
|
158
|
-
case "mysql": return "MySQL";
|
|
159
|
-
case "clickhouse": return "ClickHouse";
|
|
160
|
-
case "snowflake": return "Snowflake";
|
|
161
|
-
case "duckdb": return "DuckDB";
|
|
162
|
-
case "salesforce": return "Salesforce (SOQL)";
|
|
163
|
-
default: { const _: never = dbType; return _; }
|
|
164
|
-
}
|
|
95
|
+
return DIALECT_DISPLAY_NAMES[dbType] ?? dbType.charAt(0).toUpperCase() + dbType.slice(1);
|
|
165
96
|
}
|
|
166
97
|
|
|
167
98
|
function buildMultiSourceSection(
|
|
@@ -214,34 +145,30 @@ ${lines.join("\n")}
|
|
|
214
145
|
return section;
|
|
215
146
|
}
|
|
216
147
|
|
|
217
|
-
/**
|
|
218
|
-
* Collect Salesforce source metadata via dynamic import to avoid a hard
|
|
219
|
-
* dependency on the salesforce module (it may not be installed).
|
|
220
|
-
*/
|
|
221
|
-
function getSfSourceMeta(): ConnectionMetadata[] {
|
|
222
|
-
try {
|
|
223
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
224
|
-
const { describeSalesforceSources } = require("./db/salesforce") as {
|
|
225
|
-
describeSalesforceSources: () => ConnectionMetadata[];
|
|
226
|
-
};
|
|
227
|
-
return describeSalesforceSources();
|
|
228
|
-
} catch (err) {
|
|
229
|
-
// jsforce not installed — expected, don't log
|
|
230
|
-
if (err instanceof Error && err.message.includes("Cannot find module")) return [];
|
|
231
|
-
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Failed to describe Salesforce sources");
|
|
232
|
-
return [];
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
148
|
function appendDialectHints(prompt: string): string {
|
|
237
149
|
const hints = getDialectHints();
|
|
238
150
|
if (hints.length === 0) return prompt;
|
|
239
151
|
return prompt + "\n\n## Additional SQL Dialect Notes\n\n" + hints.map((h) => h.dialect).join("\n\n");
|
|
240
152
|
}
|
|
241
153
|
|
|
154
|
+
const PYTHON_GUIDANCE = `
|
|
155
|
+
## SQL vs Python
|
|
156
|
+
|
|
157
|
+
**Use SQL for:** filtering, aggregation, joins, window functions, GROUP BY, HAVING — anything the database handles natively.
|
|
158
|
+
**Use Python for:** statistical analysis (correlations, regressions, hypothesis tests), complex reshaping (pivots, multi-index), time series decomposition, clustering, and advanced visualizations (heatmaps, scatter matrices, violin plots).
|
|
159
|
+
|
|
160
|
+
**Anti-patterns to avoid:** SELECT * then aggregate in pandas, re-implementing GROUP BY or window functions in Python, using Python for simple counts/sums.
|
|
161
|
+
|
|
162
|
+
**Chart guidance:** prefer \`_atlas_chart\` (interactive Recharts) for bar/line/pie charts. Use \`chart_path()\` only for advanced matplotlib visualizations that Recharts cannot render.`;
|
|
163
|
+
|
|
242
164
|
function buildSystemPrompt(registry: ToolRegistry): string {
|
|
243
165
|
let base = SYSTEM_PROMPT_PREFIX + "\n\n" + registry.describe() + "\n\n" + SYSTEM_PROMPT_SUFFIX;
|
|
244
166
|
|
|
167
|
+
// Add Python guidance only when the tool is available
|
|
168
|
+
if (registry.get("executePython")) {
|
|
169
|
+
base += "\n" + PYTHON_GUIDANCE;
|
|
170
|
+
}
|
|
171
|
+
|
|
245
172
|
// Append the pre-indexed semantic layer summary
|
|
246
173
|
const semanticIndex = getSemanticIndex();
|
|
247
174
|
if (semanticIndex) {
|
|
@@ -253,9 +180,7 @@ function buildSystemPrompt(registry: ToolRegistry): string {
|
|
|
253
180
|
if (fragments.length > 0) {
|
|
254
181
|
base += "\n\n" + fragments.join("\n\n");
|
|
255
182
|
}
|
|
256
|
-
const
|
|
257
|
-
const sfMeta = getSfSourceMeta();
|
|
258
|
-
const meta = [...connectionMeta, ...sfMeta];
|
|
183
|
+
const meta = connections.describe();
|
|
259
184
|
|
|
260
185
|
// Single-connection: identical to pre-v0.7 behavior
|
|
261
186
|
if (meta.length <= 1) {
|
|
@@ -268,26 +193,21 @@ function buildSystemPrompt(registry: ToolRegistry): string {
|
|
|
268
193
|
log.debug({ err: err instanceof Error ? err.message : String(err) }, "Could not detect DB type — omitting dialect guide");
|
|
269
194
|
return appendDialectHints(base);
|
|
270
195
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
case "snowflake": return appendDialectHints(base + SNOWFLAKE_DIALECT_GUIDE);
|
|
276
|
-
case "duckdb": return appendDialectHints(base + DUCKDB_DIALECT_GUIDE);
|
|
277
|
-
case "salesforce": return appendDialectHints(base + SOQL_DIALECT_GUIDE);
|
|
278
|
-
default: { const _exhaustive: never = dbType; throw new Error(`Unknown: ${_exhaustive}`); }
|
|
196
|
+
// Core adapters get their dialect guide inline; everything else is
|
|
197
|
+
// handled by plugin dialect hints via appendDialectHints().
|
|
198
|
+
if (dbType === "mysql") {
|
|
199
|
+
return appendDialectHints(base + MYSQL_DIALECT_GUIDE);
|
|
279
200
|
}
|
|
201
|
+
return appendDialectHints(base);
|
|
280
202
|
}
|
|
281
203
|
|
|
282
|
-
// Multi-connection: list sources + include
|
|
204
|
+
// Multi-connection: list sources + include core dialect guides
|
|
283
205
|
let prompt = base + "\n\n" + buildMultiSourceSection(meta);
|
|
284
206
|
|
|
285
207
|
const dbTypes = new Set(meta.map((m) => m.dbType));
|
|
286
208
|
if (dbTypes.has("mysql")) prompt += MYSQL_DIALECT_GUIDE;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (dbTypes.has("duckdb")) prompt += DUCKDB_DIALECT_GUIDE;
|
|
290
|
-
if (dbTypes.has("salesforce")) prompt += SOQL_DIALECT_GUIDE;
|
|
209
|
+
// Non-core dialects (clickhouse, snowflake, duckdb, salesforce, etc.)
|
|
210
|
+
// are provided by plugins via appendDialectHints().
|
|
291
211
|
|
|
292
212
|
return appendDialectHints(prompt);
|
|
293
213
|
}
|
|
@@ -438,7 +358,7 @@ export async function runAgent({
|
|
|
438
358
|
maxOutputTokens: 4096,
|
|
439
359
|
stopWhen: stepCountIs(25),
|
|
440
360
|
// totalMs: 180s for self-hosted (full agent loop budget).
|
|
441
|
-
// On Vercel, maxDuration caps the serverless function at
|
|
361
|
+
// On Vercel, maxDuration caps the serverless function at 300s (Pro plan).
|
|
442
362
|
timeout: { totalMs: 180_000, stepMs: 30_000, chunkMs: 5_000 },
|
|
443
363
|
|
|
444
364
|
onError: ({ error }) => {
|
|
@@ -56,6 +56,7 @@ const MANAGED_VARS = [
|
|
|
56
56
|
"BETTER_AUTH_SECRET",
|
|
57
57
|
"ATLAS_AUTH_JWKS_URL",
|
|
58
58
|
"ATLAS_API_KEY",
|
|
59
|
+
"ATLAS_ADMIN_EMAIL",
|
|
59
60
|
] as const;
|
|
60
61
|
|
|
61
62
|
const saved: Record<string, string | undefined> = {};
|
|
@@ -74,6 +75,7 @@ beforeEach(() => {
|
|
|
74
75
|
delete process.env.BETTER_AUTH_SECRET;
|
|
75
76
|
delete process.env.ATLAS_AUTH_JWKS_URL;
|
|
76
77
|
delete process.env.ATLAS_API_KEY;
|
|
78
|
+
delete process.env.ATLAS_ADMIN_EMAIL;
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
afterEach(() => {
|
|
@@ -99,7 +101,7 @@ describe("migrateAuthTables", () => {
|
|
|
99
101
|
|
|
100
102
|
await migrateAuthTables();
|
|
101
103
|
|
|
102
|
-
// migrateInternalDB: 3 audit_log + 4 conversations/messages + 2 starred column + 3 slack + 5 action_log + 2 source tracking =
|
|
104
|
+
// migrateInternalDB: 3 audit_log + 4 conversations/messages + 2 starred column + 3 slack + 5 action_log + 2 source tracking + 7 scheduled_tasks = 26 queries
|
|
103
105
|
expect(queries.length).toBe(26);
|
|
104
106
|
expect(queries[0]).toContain("CREATE TABLE IF NOT EXISTS audit_log");
|
|
105
107
|
});
|
|
@@ -141,8 +143,8 @@ describe("migrateAuthTables", () => {
|
|
|
141
143
|
await migrateAuthTables();
|
|
142
144
|
await migrateAuthTables();
|
|
143
145
|
|
|
144
|
-
// Internal DB migration runs once (26 queries
|
|
145
|
-
expect(queries.length).toBe(
|
|
146
|
+
// Internal DB migration runs once (26 queries) + 1 ALTER TABLE for password_change_required (managed mode)
|
|
147
|
+
expect(queries.length).toBe(27);
|
|
146
148
|
// Better Auth migration runs once
|
|
147
149
|
expect(getMigrationCount()).toBe(1);
|
|
148
150
|
});
|
|
@@ -156,6 +156,28 @@ export function _setValidatorOverrides(overrides: {
|
|
|
156
156
|
_byotOverride = overrides.byot ?? null;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Categorize an auth error for diagnostic logging.
|
|
161
|
+
* Helps operators quickly identify whether a failure is a database issue,
|
|
162
|
+
* network problem, configuration error, or a programming bug.
|
|
163
|
+
*/
|
|
164
|
+
function categorizeAuthError(err: unknown): string {
|
|
165
|
+
if (err instanceof TypeError || err instanceof ReferenceError || err instanceof SyntaxError) {
|
|
166
|
+
return "programming-error";
|
|
167
|
+
}
|
|
168
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
169
|
+
if (/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET|fetch failed/i.test(msg)) {
|
|
170
|
+
return "network-error";
|
|
171
|
+
}
|
|
172
|
+
if (/relation.*does not exist|database|SQLITE|pg_|connect|pool/i.test(msg)) {
|
|
173
|
+
return "database-error";
|
|
174
|
+
}
|
|
175
|
+
if (/secret|config|env|missing|undefined|not set/i.test(msg)) {
|
|
176
|
+
return "config-error";
|
|
177
|
+
}
|
|
178
|
+
return "unknown";
|
|
179
|
+
}
|
|
180
|
+
|
|
159
181
|
/** Authenticate an incoming request based on the detected auth mode. */
|
|
160
182
|
export async function authenticateRequest(req: Request): Promise<AuthResult> {
|
|
161
183
|
const mode = detectAuthMode();
|
|
@@ -171,9 +193,11 @@ export async function authenticateRequest(req: Request): Promise<AuthResult> {
|
|
|
171
193
|
try {
|
|
172
194
|
return await (_managedOverride ?? validateManaged)(req);
|
|
173
195
|
} catch (err) {
|
|
196
|
+
const category = categorizeAuthError(err);
|
|
174
197
|
log.error(
|
|
175
|
-
{ err: err instanceof Error ? err : new Error(String(err)), mode },
|
|
176
|
-
"Managed auth error",
|
|
198
|
+
{ err: err instanceof Error ? err : new Error(String(err)), mode, category },
|
|
199
|
+
"Managed auth error (%s)",
|
|
200
|
+
category,
|
|
177
201
|
);
|
|
178
202
|
if (err instanceof TypeError || err instanceof ReferenceError || err instanceof SyntaxError) {
|
|
179
203
|
log.error({ err, mode }, "BUG: Unexpected programming error in auth validator");
|
|
@@ -190,9 +214,11 @@ export async function authenticateRequest(req: Request): Promise<AuthResult> {
|
|
|
190
214
|
try {
|
|
191
215
|
return await (_byotOverride ?? validateBYOT)(req);
|
|
192
216
|
} catch (err) {
|
|
217
|
+
const category = categorizeAuthError(err);
|
|
193
218
|
log.error(
|
|
194
|
-
{ err: err instanceof Error ? err : new Error(String(err)), mode },
|
|
195
|
-
"BYOT auth error",
|
|
219
|
+
{ err: err instanceof Error ? err : new Error(String(err)), mode, category },
|
|
220
|
+
"BYOT auth error (%s)",
|
|
221
|
+
category,
|
|
196
222
|
);
|
|
197
223
|
if (err instanceof TypeError || err instanceof ReferenceError || err instanceof SyntaxError) {
|
|
198
224
|
log.error({ err, mode }, "BUG: Unexpected programming error in auth validator");
|
|
@@ -63,7 +63,20 @@ export async function migrateAuthTables(): Promise<void> {
|
|
|
63
63
|
const ctx = await auth.$context;
|
|
64
64
|
await ctx.runMigrations();
|
|
65
65
|
log.info("Better Auth migration complete");
|
|
66
|
+
|
|
67
|
+
// Add password_change_required column to Better Auth's user table.
|
|
68
|
+
// Must run AFTER Better Auth migrations (which create the "user" table).
|
|
69
|
+
try {
|
|
70
|
+
await internalQuery(
|
|
71
|
+
`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS password_change_required BOOLEAN NOT NULL DEFAULT false`,
|
|
72
|
+
);
|
|
73
|
+
} catch {
|
|
74
|
+
log.warn("Could not add password_change_required column — password change enforcement will be skipped");
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
await bootstrapAdminUser();
|
|
78
|
+
await seedDevUser(auth);
|
|
79
|
+
await backfillPasswordChangeFlag();
|
|
67
80
|
} catch (err) {
|
|
68
81
|
log.error({ err }, "Better Auth migration failed — managed auth may not work");
|
|
69
82
|
_migrationError = "Connected to the internal database but Better Auth migration failed. Managed auth may not work. Check database permissions (CREATE TABLE).";
|
|
@@ -104,6 +117,90 @@ async function bootstrapAdminUser(): Promise<void> {
|
|
|
104
117
|
}
|
|
105
118
|
}
|
|
106
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Seed a default dev admin account when no users exist.
|
|
122
|
+
* Only runs when ATLAS_ADMIN_EMAIL is set — uses that email with a
|
|
123
|
+
* well-known password ("atlas-dev"). The databaseHook in server.ts
|
|
124
|
+
* promotes this user to admin on creation.
|
|
125
|
+
*
|
|
126
|
+
* Skips silently if any users already exist (idempotent).
|
|
127
|
+
*/
|
|
128
|
+
async function seedDevUser(auth: { api: Record<string, unknown> }): Promise<void> {
|
|
129
|
+
const adminEmail = process.env.ATLAS_ADMIN_EMAIL?.toLowerCase().trim();
|
|
130
|
+
if (!adminEmail) return;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const userCount = await internalQuery<{ count: string }>(
|
|
134
|
+
`SELECT COUNT(*) as count FROM "user"`,
|
|
135
|
+
);
|
|
136
|
+
if (parseInt(String(userCount[0]?.count ?? "0"), 10) > 0) return;
|
|
137
|
+
|
|
138
|
+
// Use Better Auth's createUser API (from the admin plugin)
|
|
139
|
+
const createUser = auth.api.createUser as (opts: {
|
|
140
|
+
body: { email: string; password: string; name: string; role: string };
|
|
141
|
+
}) => Promise<unknown>;
|
|
142
|
+
|
|
143
|
+
await createUser({
|
|
144
|
+
body: {
|
|
145
|
+
email: adminEmail,
|
|
146
|
+
password: "atlas-dev",
|
|
147
|
+
name: "Atlas Admin",
|
|
148
|
+
role: "admin",
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Mark the seeded user as requiring a password change
|
|
153
|
+
await internalQuery(
|
|
154
|
+
`UPDATE "user" SET password_change_required = true WHERE LOWER(email) = $1`,
|
|
155
|
+
[adminEmail],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
log.info({ email: adminEmail }, "Dev admin account seeded (password: atlas-dev)");
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// User might already exist from a previous partial boot — not fatal
|
|
161
|
+
log.debug({ err }, "Dev user seed skipped or failed");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Backfill: if the dev admin user exists with the default password and
|
|
167
|
+
* password_change_required is false, set the flag. Handles upgrades where
|
|
168
|
+
* the column was added after the user was already seeded.
|
|
169
|
+
*/
|
|
170
|
+
async function backfillPasswordChangeFlag(): Promise<void> {
|
|
171
|
+
const adminEmail = process.env.ATLAS_ADMIN_EMAIL?.toLowerCase().trim();
|
|
172
|
+
if (!adminEmail) return;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Only backfill if the user exists and doesn't already have the flag set
|
|
176
|
+
const rows = await internalQuery<{ id: string; password_change_required: boolean }>(
|
|
177
|
+
`SELECT u.id, u.password_change_required FROM "user" u
|
|
178
|
+
JOIN "account" a ON a."userId" = u.id AND a."providerId" = 'credential'
|
|
179
|
+
WHERE LOWER(u.email) = $1`,
|
|
180
|
+
[adminEmail],
|
|
181
|
+
);
|
|
182
|
+
if (rows.length === 0 || rows[0].password_change_required) return;
|
|
183
|
+
|
|
184
|
+
// Check if the password is still the default "atlas-dev"
|
|
185
|
+
const account = await internalQuery<{ password: string }>(
|
|
186
|
+
`SELECT password FROM "account" WHERE "userId" = $1 AND "providerId" = 'credential'`,
|
|
187
|
+
[rows[0].id],
|
|
188
|
+
);
|
|
189
|
+
if (account.length === 0 || !account[0].password) return;
|
|
190
|
+
|
|
191
|
+
const isDefault = await Bun.password.verify("atlas-dev", account[0].password);
|
|
192
|
+
if (!isDefault) return;
|
|
193
|
+
|
|
194
|
+
await internalQuery(
|
|
195
|
+
`UPDATE "user" SET password_change_required = true WHERE id = $1`,
|
|
196
|
+
[rows[0].id],
|
|
197
|
+
);
|
|
198
|
+
log.info({ email: adminEmail }, "Backfill: flagged dev admin for password change");
|
|
199
|
+
} catch (err) {
|
|
200
|
+
log.debug({ err }, "Backfill password change flag skipped");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
107
204
|
/** Reset migration state. For testing only. */
|
|
108
205
|
export function resetMigrationState(): void {
|
|
109
206
|
_migrated = false;
|
|
@@ -71,12 +71,23 @@ export function getAuthInstance(): AuthInstance {
|
|
|
71
71
|
} catch { /* ignore malformed URL */ }
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Resolve base URL: explicit env var > Vercel auto-detect > undefined (Better Auth auto-detect).
|
|
75
|
+
// On Vercel, VERCEL_PROJECT_PRODUCTION_URL or VERCEL_URL are always set.
|
|
76
|
+
// Without a baseURL, Better Auth logs a noisy warning on every cold start.
|
|
77
|
+
const baseURL =
|
|
78
|
+
process.env.BETTER_AUTH_URL ||
|
|
79
|
+
(process.env.VERCEL_PROJECT_PRODUCTION_URL
|
|
80
|
+
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
|
81
|
+
: process.env.VERCEL_URL
|
|
82
|
+
? `https://${process.env.VERCEL_URL}`
|
|
83
|
+
: undefined);
|
|
84
|
+
|
|
74
85
|
const instance = betterAuth({
|
|
75
86
|
// getInternalDB() returns a pg.Pool typed as InternalPool.
|
|
76
87
|
// Cast needed because Better Auth expects its own pool/adapter type.
|
|
77
88
|
database: getInternalDB() as unknown as Parameters<typeof betterAuth>[0]["database"],
|
|
78
89
|
secret,
|
|
79
|
-
baseURL
|
|
90
|
+
baseURL,
|
|
80
91
|
emailAndPassword: {
|
|
81
92
|
enabled: true,
|
|
82
93
|
requireEmailVerification: false,
|
|
@@ -48,9 +48,9 @@ const RateLimitConfigSchema = z.object({
|
|
|
48
48
|
export type RateLimitConfig = z.infer<typeof RateLimitConfigSchema>;
|
|
49
49
|
|
|
50
50
|
const DatasourceConfigSchema = z.object({
|
|
51
|
-
/** Database connection string (postgresql
|
|
51
|
+
/** Database connection string (postgresql:// or mysql:// for core; other schemes via plugins). */
|
|
52
52
|
url: z.string().min(1, "Datasource URL must not be empty"),
|
|
53
|
-
/** PostgreSQL schema name (sets search_path). Ignored for MySQL
|
|
53
|
+
/** PostgreSQL schema name (sets search_path). Ignored for MySQL and plugin-managed connections. */
|
|
54
54
|
schema: z.string().optional(),
|
|
55
55
|
/** Human-readable description shown in the agent system prompt. */
|
|
56
56
|
description: z.string().optional(),
|
|
@@ -167,7 +167,7 @@ const AtlasConfigSchema = z.object({
|
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
169
|
* Plugin instances to register at boot. Each element should satisfy
|
|
170
|
-
* the `AtlasPlugin` interface from `@
|
|
170
|
+
* the `AtlasPlugin` interface from `@useatlas/plugin-sdk`.
|
|
171
171
|
*
|
|
172
172
|
* Zod validates structural shape (id, type, version) at config load
|
|
173
173
|
* time. Plugin-level configSchema validation happens at factory call
|
|
@@ -379,6 +379,7 @@ async function tryLoadConfigFile(
|
|
|
379
379
|
);
|
|
380
380
|
}
|
|
381
381
|
const raw = mod.default;
|
|
382
|
+
log.debug({ file: filePath }, "Config file loaded successfully");
|
|
382
383
|
return raw;
|
|
383
384
|
} catch (err) {
|
|
384
385
|
// If the file exists but fails to parse/import, that is a hard error
|
|
@@ -398,7 +399,7 @@ async function tryLoadConfigFile(
|
|
|
398
399
|
// Plugin shape validation
|
|
399
400
|
// ---------------------------------------------------------------------------
|
|
400
401
|
|
|
401
|
-
const VALID_PLUGIN_TYPES = new Set(["datasource", "context", "interaction", "action"]);
|
|
402
|
+
const VALID_PLUGIN_TYPES = new Set(["datasource", "context", "interaction", "action", "sandbox"]);
|
|
402
403
|
|
|
403
404
|
/**
|
|
404
405
|
* Validate plugin array entries have the required structural shape:
|
|
@@ -446,11 +447,17 @@ function validatePlugins(plugins: unknown[]): void {
|
|
|
446
447
|
}
|
|
447
448
|
}
|
|
448
449
|
|
|
449
|
-
//
|
|
450
|
-
if (!("
|
|
451
|
-
errors.push(`${label} is missing "
|
|
452
|
-
} else if (
|
|
453
|
-
errors.push(`${label} has
|
|
450
|
+
// types
|
|
451
|
+
if (!("types" in obj) || !Array.isArray(obj.types)) {
|
|
452
|
+
errors.push(`${label} is missing "types" (array of plugin types)`);
|
|
453
|
+
} else if (obj.types.length === 0) {
|
|
454
|
+
errors.push(`${label} has an empty "types" array — must contain at least one plugin type`);
|
|
455
|
+
} else {
|
|
456
|
+
for (const t of obj.types) {
|
|
457
|
+
if (typeof t !== "string" || !VALID_PLUGIN_TYPES.has(t)) {
|
|
458
|
+
errors.push(`${label} has invalid type "${t}" in "types" — must be one of: ${[...VALID_PLUGIN_TYPES].join(", ")}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
454
461
|
}
|
|
455
462
|
|
|
456
463
|
// version
|
|
@@ -575,44 +582,35 @@ export async function applyDatasources(
|
|
|
575
582
|
}
|
|
576
583
|
|
|
577
584
|
const connRegistry = registry ?? (await import("./db/connection")).connections;
|
|
578
|
-
const { detectDBType } = await import("./db/connection");
|
|
579
585
|
|
|
580
586
|
connRegistry.setMaxTotalConnections(config.maxTotalConnections);
|
|
581
587
|
|
|
582
588
|
for (const [id, ds] of Object.entries(config.datasources)) {
|
|
583
589
|
try {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
idleTimeoutMs: ds.idleTimeoutMs,
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// Fire initial health check — logs on failure but does not block startup.
|
|
601
|
-
// A degraded connection is still usable (the DB may recover).
|
|
602
|
-
connRegistry.healthCheck(id).then((result) => {
|
|
603
|
-
if (result.status !== "healthy") {
|
|
604
|
-
log.warn(
|
|
605
|
-
{ connectionId: id, status: result.status, message: result.message },
|
|
606
|
-
"Datasource registered but initial health check failed — connection may be misconfigured",
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
}).catch((healthErr) => {
|
|
590
|
+
log.info({ connectionId: id }, "Registering datasource from config");
|
|
591
|
+
connRegistry.register(id, {
|
|
592
|
+
url: ds.url,
|
|
593
|
+
schema: ds.schema,
|
|
594
|
+
description: ds.description,
|
|
595
|
+
maxConnections: ds.maxConnections,
|
|
596
|
+
idleTimeoutMs: ds.idleTimeoutMs,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Fire initial health check — logs on failure but does not block startup.
|
|
600
|
+
// A degraded connection is still usable (the DB may recover).
|
|
601
|
+
connRegistry.healthCheck(id).then((result) => {
|
|
602
|
+
if (result.status !== "healthy") {
|
|
610
603
|
log.warn(
|
|
611
|
-
{
|
|
612
|
-
"
|
|
604
|
+
{ connectionId: id, status: result.status, message: result.message },
|
|
605
|
+
"Datasource registered but initial health check failed — connection may be misconfigured",
|
|
613
606
|
);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
607
|
+
}
|
|
608
|
+
}).catch((healthErr) => {
|
|
609
|
+
log.warn(
|
|
610
|
+
{ err: healthErr instanceof Error ? healthErr.message : String(healthErr), connectionId: id },
|
|
611
|
+
"Initial health check failed after registration",
|
|
612
|
+
);
|
|
613
|
+
});
|
|
616
614
|
|
|
617
615
|
if (ds.rateLimit) {
|
|
618
616
|
const { registerSourceRateLimit } = await import("./db/source-rate-limit");
|