@useatlas/create 0.0.1
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 +231 -0
- package/index.ts +829 -0
- package/package.json +38 -0
- package/templates/docker/.env.example +67 -0
- package/templates/docker/Dockerfile +52 -0
- package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/docker/bin/__tests__/eval.test.ts +434 -0
- package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/docker/bin/__tests__/query.test.ts +240 -0
- package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/docker/bin/atlas.ts +5044 -0
- package/templates/docker/bin/benchmark.ts +695 -0
- package/templates/docker/bin/enrich.ts +559 -0
- package/templates/docker/bin/eval.ts +770 -0
- package/templates/docker/bin/smoke.ts +438 -0
- package/templates/docker/data/.gitkeep +0 -0
- package/templates/docker/data/cybersec.sql +1961 -0
- package/templates/docker/data/demo-semantic/catalog.yml +40 -0
- package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
- package/templates/docker/data/demo-semantic/glossary.yml +22 -0
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/docker/data/demo.sql +373 -0
- package/templates/docker/data/ecommerce.sql +1690 -0
- package/templates/docker/data/init-demo-db.sql +8 -0
- package/templates/docker/docker-compose.yml +34 -0
- package/templates/docker/docs/deploy.md +390 -0
- package/templates/docker/eslint.config.mjs +18 -0
- package/templates/docker/gitignore +5 -0
- package/templates/docker/next.config.ts +9 -0
- package/templates/docker/package.json +59 -0
- package/templates/docker/postcss.config.mjs +8 -0
- package/templates/docker/public/.gitkeep +0 -0
- package/templates/docker/public/favicon.svg +4 -0
- package/templates/docker/railway.json +13 -0
- package/templates/docker/render.yaml +34 -0
- package/templates/docker/semantic/catalog.yml +5 -0
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/glossary.yml +6 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/sidecar/Dockerfile +28 -0
- package/templates/docker/sidecar/railway.json +14 -0
- package/templates/docker/sidecar/server.ts +188 -0
- package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
- package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
- package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
- package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
- package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/docker/src/api/__tests__/health.test.ts +261 -0
- package/templates/docker/src/api/__tests__/query.test.ts +891 -0
- package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
- package/templates/docker/src/api/index.ts +117 -0
- package/templates/docker/src/api/routes/actions.ts +274 -0
- package/templates/docker/src/api/routes/admin.ts +757 -0
- package/templates/docker/src/api/routes/auth.ts +48 -0
- package/templates/docker/src/api/routes/chat.ts +465 -0
- package/templates/docker/src/api/routes/conversations.ts +266 -0
- package/templates/docker/src/api/routes/health.ts +287 -0
- package/templates/docker/src/api/routes/openapi.ts +390 -0
- package/templates/docker/src/api/routes/query.ts +318 -0
- package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/docker/src/api/routes/slack.ts +611 -0
- package/templates/docker/src/api/server.ts +226 -0
- package/templates/docker/src/app/api/[...route]/route.ts +33 -0
- package/templates/docker/src/app/error.tsx +24 -0
- package/templates/docker/src/app/globals.css +126 -0
- package/templates/docker/src/app/layout.tsx +19 -0
- package/templates/docker/src/app/page.tsx +14 -0
- package/templates/docker/src/global.d.ts +1 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/docker/src/lib/action-types.ts +95 -0
- package/templates/docker/src/lib/agent-query.ts +178 -0
- package/templates/docker/src/lib/agent.ts +505 -0
- package/templates/docker/src/lib/api-url.ts +2 -0
- package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/docker/src/lib/auth/audit.ts +89 -0
- package/templates/docker/src/lib/auth/byot.ts +158 -0
- package/templates/docker/src/lib/auth/client.ts +35 -0
- package/templates/docker/src/lib/auth/detect.ts +83 -0
- package/templates/docker/src/lib/auth/managed.ts +73 -0
- package/templates/docker/src/lib/auth/middleware.ts +208 -0
- package/templates/docker/src/lib/auth/migrate.ts +111 -0
- package/templates/docker/src/lib/auth/permissions.ts +156 -0
- package/templates/docker/src/lib/auth/server.ts +142 -0
- package/templates/docker/src/lib/auth/simple-key.ts +92 -0
- package/templates/docker/src/lib/auth/types.ts +49 -0
- package/templates/docker/src/lib/config.ts +704 -0
- package/templates/docker/src/lib/conversation-types.ts +29 -0
- package/templates/docker/src/lib/conversations.ts +270 -0
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/docker/src/lib/db/connection.ts +753 -0
- package/templates/docker/src/lib/db/duckdb.ts +122 -0
- package/templates/docker/src/lib/db/internal.ts +273 -0
- package/templates/docker/src/lib/db/salesforce.ts +342 -0
- package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/docker/src/lib/errors.ts +154 -0
- package/templates/docker/src/lib/logger.ts +98 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/docker/src/lib/plugins/hooks.ts +162 -0
- package/templates/docker/src/lib/plugins/index.ts +9 -0
- package/templates/docker/src/lib/plugins/migrate.ts +309 -0
- package/templates/docker/src/lib/plugins/registry.ts +231 -0
- package/templates/docker/src/lib/plugins/tools.ts +39 -0
- package/templates/docker/src/lib/plugins/wiring.ts +291 -0
- package/templates/docker/src/lib/providers.ts +102 -0
- package/templates/docker/src/lib/rls.ts +321 -0
- package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
- package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
- package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
- package/templates/docker/src/lib/scheduler/engine.ts +317 -0
- package/templates/docker/src/lib/scheduler/executor.ts +73 -0
- package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
- package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/docker/src/lib/scheduler/index.ts +7 -0
- package/templates/docker/src/lib/security.ts +11 -0
- package/templates/docker/src/lib/semantic-index.ts +503 -0
- package/templates/docker/src/lib/semantic.ts +387 -0
- package/templates/docker/src/lib/sidecar-types.ts +16 -0
- package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/docker/src/lib/slack/api.ts +102 -0
- package/templates/docker/src/lib/slack/format.ts +209 -0
- package/templates/docker/src/lib/slack/store.ts +107 -0
- package/templates/docker/src/lib/slack/threads.ts +64 -0
- package/templates/docker/src/lib/slack/verify.ts +71 -0
- package/templates/docker/src/lib/startup.ts +730 -0
- package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
- package/templates/docker/src/lib/tools/actions/email.ts +191 -0
- package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
- package/templates/docker/src/lib/tools/actions/index.ts +23 -0
- package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
- package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/docker/src/lib/tools/explore.ts +379 -0
- package/templates/docker/src/lib/tools/registry.ts +221 -0
- package/templates/docker/src/lib/tools/salesforce.ts +138 -0
- package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
- package/templates/docker/src/lib/tools/sql.ts +680 -0
- package/templates/docker/src/lib/tracing.ts +40 -0
- package/templates/docker/src/lib/utils.ts +6 -0
- package/templates/docker/src/test-setup.ts +38 -0
- package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
- package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/docker/src/ui/context.tsx +78 -0
- package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/docker/src/ui/lib/action-types.ts +63 -0
- package/templates/docker/src/ui/lib/helpers.ts +104 -0
- package/templates/docker/src/ui/lib/types.ts +145 -0
- package/templates/docker/tsconfig.json +41 -0
- package/templates/docker/vercel.json +3 -0
- package/templates/nextjs-standalone/.env.example +68 -0
- package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
- package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
- package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
- package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
- package/templates/nextjs-standalone/bin/enrich.ts +559 -0
- package/templates/nextjs-standalone/bin/eval.ts +770 -0
- package/templates/nextjs-standalone/bin/smoke.ts +438 -0
- package/templates/nextjs-standalone/data/.gitkeep +0 -0
- package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/nextjs-standalone/data/demo.sql +373 -0
- package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
- package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
- package/templates/nextjs-standalone/docs/deploy.md +390 -0
- package/templates/nextjs-standalone/eslint.config.mjs +18 -0
- package/templates/nextjs-standalone/gitignore +5 -0
- package/templates/nextjs-standalone/next.config.ts +10 -0
- package/templates/nextjs-standalone/package.json +63 -0
- package/templates/nextjs-standalone/postcss.config.mjs +8 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
- package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
- package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
- package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
- package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
- package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
- package/templates/nextjs-standalone/src/api/index.ts +117 -0
- package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
- package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
- package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
- package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
- package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
- package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
- package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
- package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
- package/templates/nextjs-standalone/src/api/server.ts +226 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
- package/templates/nextjs-standalone/src/app/error.tsx +24 -0
- package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
- package/templates/nextjs-standalone/src/app/globals.css +126 -0
- package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
- package/templates/nextjs-standalone/src/app/page.tsx +14 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
- package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
- package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
- package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
- package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
- package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
- package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
- package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
- package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
- package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
- package/templates/nextjs-standalone/src/lib/config.ts +704 -0
- package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
- package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
- package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
- package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
- package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
- package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
- package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
- package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
- package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
- package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
- package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
- package/templates/nextjs-standalone/src/lib/security.ts +11 -0
- package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
- package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
- package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
- package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
- package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
- package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
- package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
- package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
- package/templates/nextjs-standalone/src/test-setup.ts +38 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
- package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
- package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
- package/templates/nextjs-standalone/tsconfig.json +32 -0
- package/templates/nextjs-standalone/vercel.json +4 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Validates environment configuration on first API request and returns
|
|
5
|
+
* clear, actionable error messages. Never exposes secrets or stack traces.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import { detectDBType } from "./db/connection";
|
|
11
|
+
import { detectAuthMode, getAuthModeSource } from "./auth/detect";
|
|
12
|
+
import { createLogger } from "./logger";
|
|
13
|
+
|
|
14
|
+
const log = createLogger("startup");
|
|
15
|
+
|
|
16
|
+
export type DiagnosticCode =
|
|
17
|
+
| "MISSING_DATASOURCE_URL" | "DB_UNREACHABLE" | "MISSING_API_KEY"
|
|
18
|
+
| "MISSING_SEMANTIC_LAYER" | "INVALID_SCHEMA" | "INTERNAL_DB_UNREACHABLE"
|
|
19
|
+
| "WEAK_AUTH_SECRET" | "INVALID_JWKS_URL" | "MISSING_AUTH_ISSUER"
|
|
20
|
+
| "MISSING_AUTH_PREREQ"
|
|
21
|
+
| "ACTIONS_REQUIRE_AUTH" | "ACTIONS_MISSING_CREDENTIALS";
|
|
22
|
+
|
|
23
|
+
export interface DiagnosticError {
|
|
24
|
+
code: DiagnosticCode;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PROVIDER_KEY_MAP: Record<string, string> = {
|
|
29
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
30
|
+
openai: "OPENAI_API_KEY",
|
|
31
|
+
bedrock: "AWS_ACCESS_KEY_ID",
|
|
32
|
+
ollama: "", // Ollama runs locally, no API key required
|
|
33
|
+
gateway: "AI_GATEWAY_API_KEY",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let _cached: DiagnosticError[] | null = null;
|
|
37
|
+
let _cachedAt = 0;
|
|
38
|
+
const _startupWarnings: string[] = [];
|
|
39
|
+
const ERROR_CACHE_TTL_MS = 30_000;
|
|
40
|
+
|
|
41
|
+
/** Non-blocking warnings collected during validation. */
|
|
42
|
+
export function getStartupWarnings(): readonly string[] {
|
|
43
|
+
return _startupWarnings;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Reset all cached state. For testing only. */
|
|
47
|
+
export function resetStartupCache(): void {
|
|
48
|
+
_cached = null;
|
|
49
|
+
_cachedAt = 0;
|
|
50
|
+
_startupWarnings.length = 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validate the environment and return any configuration errors.
|
|
55
|
+
* Results are cached permanently after a successful (no-error) check,
|
|
56
|
+
* meaning subsequent environment degradation (e.g., database going down)
|
|
57
|
+
* will not be detected by this function. The health endpoint's live probes
|
|
58
|
+
* (SELECT 1) provide real-time reachability checks.
|
|
59
|
+
* When errors exist, validation re-runs every 30s to detect fixes.
|
|
60
|
+
*/
|
|
61
|
+
export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
62
|
+
if (_cached !== null) {
|
|
63
|
+
if (_cached.length === 0 || Date.now() - _cachedAt < ERROR_CACHE_TTL_MS) {
|
|
64
|
+
return _cached;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const errors: DiagnosticError[] = [];
|
|
69
|
+
|
|
70
|
+
// 1. ATLAS_DATASOURCE_URL — error if DATABASE_URL looks like a migration leftover
|
|
71
|
+
if (!process.env.ATLAS_DATASOURCE_URL) {
|
|
72
|
+
if (process.env.DATABASE_URL) {
|
|
73
|
+
const msg =
|
|
74
|
+
"DATABASE_URL is set but ATLAS_DATASOURCE_URL is not. " +
|
|
75
|
+
"As of v0.5, the analytics datasource uses ATLAS_DATASOURCE_URL. " +
|
|
76
|
+
"DATABASE_URL is now reserved for Atlas's internal Postgres. " +
|
|
77
|
+
"Rename your analytics connection to ATLAS_DATASOURCE_URL.";
|
|
78
|
+
log.error(msg);
|
|
79
|
+
errors.push({ code: "MISSING_DATASOURCE_URL", message: msg });
|
|
80
|
+
} else {
|
|
81
|
+
const msg =
|
|
82
|
+
"ATLAS_DATASOURCE_URL is not set. Atlas can start without an analytics datasource, but queries will not work. " +
|
|
83
|
+
"Set it to a PostgreSQL connection string (postgresql://user:pass@host:5432/dbname) " +
|
|
84
|
+
"or a MySQL connection string (mysql://user:pass@host:3306/dbname).";
|
|
85
|
+
if (!_startupWarnings.includes(msg)) {
|
|
86
|
+
_startupWarnings.push(msg);
|
|
87
|
+
}
|
|
88
|
+
log.warn(msg);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 2. API key for configured provider
|
|
93
|
+
const provider = process.env.ATLAS_PROVIDER ?? "anthropic";
|
|
94
|
+
const requiredKey = PROVIDER_KEY_MAP[provider];
|
|
95
|
+
|
|
96
|
+
if (requiredKey === undefined) {
|
|
97
|
+
// Unknown provider — providers.ts will throw a descriptive error at model init,
|
|
98
|
+
// so we don't duplicate that check here.
|
|
99
|
+
} else if (requiredKey && !process.env[requiredKey]) {
|
|
100
|
+
let message = `${requiredKey} is not set. Atlas needs an API key for the ${provider} provider.`;
|
|
101
|
+
if (provider === "gateway") {
|
|
102
|
+
message += " Create one at https://vercel.com/~/ai/api-keys";
|
|
103
|
+
}
|
|
104
|
+
errors.push({ code: "MISSING_API_KEY", message });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 3. Semantic layer presence
|
|
108
|
+
const semanticDir = path.resolve(process.cwd(), "semantic", "entities");
|
|
109
|
+
let hasEntities = false;
|
|
110
|
+
try {
|
|
111
|
+
const files = fs.readdirSync(semanticDir);
|
|
112
|
+
hasEntities = files.some((f) => f.endsWith(".yml"));
|
|
113
|
+
} catch (err) {
|
|
114
|
+
const code = err instanceof Error && "code" in err ? (err as NodeJS.ErrnoException).code : undefined;
|
|
115
|
+
if (code !== "ENOENT") {
|
|
116
|
+
// Non-ENOENT errors (permissions, not a directory, etc.) — report the real problem
|
|
117
|
+
errors.push({
|
|
118
|
+
code: "MISSING_SEMANTIC_LAYER",
|
|
119
|
+
message: `Could not read semantic layer directory: ${err instanceof Error ? err.message : String(err)}. Check file permissions.`,
|
|
120
|
+
});
|
|
121
|
+
hasEntities = true; // prevent duplicate "no semantic layer" error below
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!hasEntities) {
|
|
125
|
+
errors.push({
|
|
126
|
+
code: "MISSING_SEMANTIC_LAYER",
|
|
127
|
+
message:
|
|
128
|
+
"No semantic layer found. Run 'bun run atlas -- init' to generate one from your database, or 'bun run atlas -- init --demo' to load demo data.",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 4. Datasource connectivity (only if ATLAS_DATASOURCE_URL is set)
|
|
133
|
+
if (process.env.ATLAS_DATASOURCE_URL) {
|
|
134
|
+
let dbType: ReturnType<typeof detectDBType> | null = null;
|
|
135
|
+
try {
|
|
136
|
+
dbType = detectDBType();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
139
|
+
log.error({ err: detail }, "Unsupported datasource URL");
|
|
140
|
+
errors.push({ code: "DB_UNREACHABLE", message: detail });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (dbType === "mysql") {
|
|
144
|
+
// MySQL: URL validation + connection test
|
|
145
|
+
if (!isValidUrl(process.env.ATLAS_DATASOURCE_URL)) {
|
|
146
|
+
errors.push({
|
|
147
|
+
code: "DB_UNREACHABLE",
|
|
148
|
+
message: "ATLAS_DATASOURCE_URL appears malformed. Expected format: mysql://user:pass@host:3306/dbname",
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
152
|
+
const mysql = require("mysql2/promise");
|
|
153
|
+
let pool;
|
|
154
|
+
try {
|
|
155
|
+
pool = mysql.createPool({
|
|
156
|
+
uri: process.env.ATLAS_DATASOURCE_URL,
|
|
157
|
+
connectionLimit: 1,
|
|
158
|
+
connectTimeout: 5000,
|
|
159
|
+
});
|
|
160
|
+
const conn = await pool.getConnection();
|
|
161
|
+
conn.release();
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const detail = err instanceof Error ? err.message : "";
|
|
164
|
+
log.error({ err: detail }, "MySQL connection check failed");
|
|
165
|
+
|
|
166
|
+
let message = "Cannot connect to the database. Check that the server is running and the connection string is correct.";
|
|
167
|
+
|
|
168
|
+
if (/ECONNREFUSED/i.test(detail)) {
|
|
169
|
+
message += " The connection was refused — is the MySQL server running?";
|
|
170
|
+
} else if (/Access denied/i.test(detail) || /ER_ACCESS_DENIED/i.test(detail)) {
|
|
171
|
+
message += " Authentication failed — check your username and password.";
|
|
172
|
+
} else if (/ER_BAD_DB_ERROR/i.test(detail)) {
|
|
173
|
+
message += " The specified database does not exist.";
|
|
174
|
+
} else if (/timeout/i.test(detail)) {
|
|
175
|
+
message += " The connection timed out — check network/firewall settings.";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
errors.push({ code: "DB_UNREACHABLE", message });
|
|
179
|
+
} finally {
|
|
180
|
+
if (pool) {
|
|
181
|
+
await pool.end().catch((err: unknown) => {
|
|
182
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Pool cleanup warning");
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else if (dbType === "clickhouse") {
|
|
188
|
+
// ClickHouse: connectivity test via SELECT 1
|
|
189
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
|
+
let createClient: any = null;
|
|
191
|
+
try {
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
193
|
+
({ createClient } = require("@clickhouse/client"));
|
|
194
|
+
} catch {
|
|
195
|
+
errors.push({
|
|
196
|
+
code: "DB_UNREACHABLE",
|
|
197
|
+
message: "ClickHouse support requires the @clickhouse/client package. Install it with: bun add @clickhouse/client",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (createClient) {
|
|
202
|
+
const { rewriteClickHouseUrl } = await import("./db/connection");
|
|
203
|
+
const httpUrl = rewriteClickHouseUrl(process.env.ATLAS_DATASOURCE_URL!);
|
|
204
|
+
const client = createClient({ url: httpUrl });
|
|
205
|
+
try {
|
|
206
|
+
await client.query({ query: "SELECT 1", format: "JSON" });
|
|
207
|
+
} catch (err) {
|
|
208
|
+
const detail = err instanceof Error ? err.message : "";
|
|
209
|
+
log.error({ err: detail }, "ClickHouse connection check failed");
|
|
210
|
+
|
|
211
|
+
let message = "Cannot connect to ClickHouse. Check that the server is running and the connection string is correct.";
|
|
212
|
+
|
|
213
|
+
if (/ECONNREFUSED/i.test(detail)) {
|
|
214
|
+
message += " The connection was refused — is the ClickHouse server running?";
|
|
215
|
+
} else if (/Authentication/i.test(detail) || /AUTHENTICATION_FAILED/i.test(detail)) {
|
|
216
|
+
message += " Authentication failed — check your username and password.";
|
|
217
|
+
} else if (/timeout/i.test(detail)) {
|
|
218
|
+
message += " The connection timed out — check network/firewall settings.";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
errors.push({ code: "DB_UNREACHABLE", message });
|
|
222
|
+
} finally {
|
|
223
|
+
await client.close().catch((err: unknown) => {
|
|
224
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "ClickHouse client cleanup warning");
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} else if (dbType === "postgres") {
|
|
229
|
+
// PostgreSQL: existing URL validation + connection test + schema validation
|
|
230
|
+
const atlasSchema = process.env.ATLAS_SCHEMA;
|
|
231
|
+
const VALID_SQL_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
232
|
+
|
|
233
|
+
// Validate ATLAS_SCHEMA format before attempting connection
|
|
234
|
+
if (atlasSchema && !VALID_SQL_IDENTIFIER.test(atlasSchema)) {
|
|
235
|
+
errors.push({
|
|
236
|
+
code: "INVALID_SCHEMA",
|
|
237
|
+
message: `Invalid ATLAS_SCHEMA "${atlasSchema}". Must be a valid SQL identifier (letters, digits, underscores).`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!isValidUrl(process.env.ATLAS_DATASOURCE_URL)) {
|
|
242
|
+
errors.push({
|
|
243
|
+
code: "DB_UNREACHABLE",
|
|
244
|
+
message: "ATLAS_DATASOURCE_URL appears malformed. Expected format: postgresql://user:pass@host:5432/dbname",
|
|
245
|
+
});
|
|
246
|
+
} else {
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
248
|
+
const { Pool } = require("pg");
|
|
249
|
+
const pool = new Pool({
|
|
250
|
+
connectionString: process.env.ATLAS_DATASOURCE_URL,
|
|
251
|
+
max: 1,
|
|
252
|
+
connectionTimeoutMillis: 5000,
|
|
253
|
+
});
|
|
254
|
+
try {
|
|
255
|
+
const client = await pool.connect();
|
|
256
|
+
|
|
257
|
+
// Verify schema exists if ATLAS_SCHEMA is set and valid
|
|
258
|
+
if (atlasSchema && atlasSchema !== "public" && VALID_SQL_IDENTIFIER.test(atlasSchema)) {
|
|
259
|
+
try {
|
|
260
|
+
const result = await client.query(
|
|
261
|
+
"SELECT 1 FROM pg_namespace WHERE nspname = $1",
|
|
262
|
+
[atlasSchema]
|
|
263
|
+
);
|
|
264
|
+
if (result.rows.length === 0) {
|
|
265
|
+
errors.push({
|
|
266
|
+
code: "INVALID_SCHEMA",
|
|
267
|
+
message: `Schema "${atlasSchema}" does not exist in the database. Check ATLAS_SCHEMA in your .env file.`,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
} catch (schemaErr) {
|
|
271
|
+
log.error({ err: schemaErr instanceof Error ? schemaErr.message : String(schemaErr) }, "Schema existence check failed");
|
|
272
|
+
errors.push({
|
|
273
|
+
code: "INVALID_SCHEMA",
|
|
274
|
+
message: `Could not verify schema "${atlasSchema}". Check ATLAS_SCHEMA and database permissions.`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
client.release();
|
|
280
|
+
} catch (err) {
|
|
281
|
+
const detail = err instanceof Error ? err.message : "";
|
|
282
|
+
log.error({ err: detail }, "DB connection check failed");
|
|
283
|
+
|
|
284
|
+
let message = "Cannot connect to the database. Check that the server is running and the connection string is correct.";
|
|
285
|
+
|
|
286
|
+
if (/ECONNREFUSED/i.test(detail)) {
|
|
287
|
+
message += " The connection was refused — is the database server running?";
|
|
288
|
+
} else if (/timeout/i.test(detail)) {
|
|
289
|
+
message += " The connection timed out — check network/firewall settings.";
|
|
290
|
+
} else if (/authentication/i.test(detail) || /password/i.test(detail)) {
|
|
291
|
+
message += " Authentication failed — check your username and password.";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
errors.push({ code: "DB_UNREACHABLE", message });
|
|
295
|
+
} finally {
|
|
296
|
+
await pool.end().catch((err: unknown) => {
|
|
297
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Pool cleanup warning");
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} else if (dbType === "salesforce") {
|
|
302
|
+
// Salesforce: test login + listObjects
|
|
303
|
+
try {
|
|
304
|
+
const { parseSalesforceURL, createSalesforceDataSource } = await import("./db/salesforce");
|
|
305
|
+
const config = parseSalesforceURL(process.env.ATLAS_DATASOURCE_URL!);
|
|
306
|
+
const source = createSalesforceDataSource(config);
|
|
307
|
+
try {
|
|
308
|
+
await source.listObjects();
|
|
309
|
+
} finally {
|
|
310
|
+
await source.close().catch((err: unknown) => {
|
|
311
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Salesforce cleanup warning");
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
} catch (err) {
|
|
315
|
+
const detail = err instanceof Error ? err.message : "";
|
|
316
|
+
log.error({ err: detail }, "Salesforce connection check failed");
|
|
317
|
+
|
|
318
|
+
let message = "Cannot connect to Salesforce. Check that your credentials and connection string are correct.";
|
|
319
|
+
|
|
320
|
+
if (/LOGIN_MUST_USE_SECURITY_TOKEN/i.test(detail)) {
|
|
321
|
+
message += " A security token is required — add ?token=YOUR_TOKEN to the connection URL.";
|
|
322
|
+
} else if (/INVALID_LOGIN/i.test(detail)) {
|
|
323
|
+
message += " Authentication failed — check your username and password.";
|
|
324
|
+
} else if (/timeout/i.test(detail)) {
|
|
325
|
+
message += " The connection timed out — check network/firewall settings.";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
errors.push({ code: "DB_UNREACHABLE", message });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Snowflake: warn that SQL validation is the sole read-only enforcement layer
|
|
333
|
+
if (dbType === "snowflake") {
|
|
334
|
+
const msg =
|
|
335
|
+
"Snowflake connections rely solely on SQL validation for read-only enforcement (no session-level read-only mode). " +
|
|
336
|
+
"Configure the Snowflake role with SELECT-only privileges for defense-in-depth.";
|
|
337
|
+
if (!_startupWarnings.includes(msg)) {
|
|
338
|
+
_startupWarnings.push(msg);
|
|
339
|
+
}
|
|
340
|
+
log.warn(msg);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 5. Internal database (DATABASE_URL) — optional, for auth/audit/settings
|
|
345
|
+
if (process.env.DATABASE_URL) {
|
|
346
|
+
if (!isValidUrl(process.env.DATABASE_URL)) {
|
|
347
|
+
errors.push({
|
|
348
|
+
code: "INTERNAL_DB_UNREACHABLE",
|
|
349
|
+
message: "DATABASE_URL appears malformed. Expected format: postgresql://user:pass@host:5432/atlas",
|
|
350
|
+
});
|
|
351
|
+
} else {
|
|
352
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
353
|
+
const { Pool } = require("pg");
|
|
354
|
+
const pool = new Pool({
|
|
355
|
+
connectionString: process.env.DATABASE_URL,
|
|
356
|
+
max: 1,
|
|
357
|
+
connectionTimeoutMillis: 5000,
|
|
358
|
+
});
|
|
359
|
+
try {
|
|
360
|
+
const client = await pool.connect();
|
|
361
|
+
client.release();
|
|
362
|
+
} catch (err) {
|
|
363
|
+
const detail = err instanceof Error ? err.message : "";
|
|
364
|
+
log.error({ err: detail }, "Internal DB connection check failed");
|
|
365
|
+
|
|
366
|
+
let message = "Cannot connect to the internal database (DATABASE_URL). Check that the server is running and the connection string is correct.";
|
|
367
|
+
if (/ECONNREFUSED/i.test(detail)) {
|
|
368
|
+
message += " The connection was refused — is the database server running?";
|
|
369
|
+
} else if (/timeout/i.test(detail)) {
|
|
370
|
+
message += " The connection timed out — check network/firewall settings.";
|
|
371
|
+
} else if (/authentication/i.test(detail) || /password/i.test(detail)) {
|
|
372
|
+
message += " Authentication failed — check your username and password.";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
errors.push({ code: "INTERNAL_DB_UNREACHABLE", message });
|
|
376
|
+
} finally {
|
|
377
|
+
await pool.end().catch((err: unknown) => {
|
|
378
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Internal DB pool cleanup warning");
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
const msg = "DATABASE_URL not set — audit log will not persist to database.";
|
|
384
|
+
if (!_startupWarnings.includes(msg)) {
|
|
385
|
+
_startupWarnings.push(msg);
|
|
386
|
+
}
|
|
387
|
+
log.warn(msg);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Check if boot-time migration reported errors
|
|
391
|
+
const { getMigrationError } = await import("@atlas/api/lib/auth/migrate");
|
|
392
|
+
const migrationErr = getMigrationError();
|
|
393
|
+
if (migrationErr) {
|
|
394
|
+
errors.push({ code: "INTERNAL_DB_UNREACHABLE", message: migrationErr });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 6. Auth mode diagnostics
|
|
398
|
+
const authMode = detectAuthMode();
|
|
399
|
+
const authSource = getAuthModeSource();
|
|
400
|
+
log.info({ authMode, source: authSource }, "Auth mode: %s (%s)", authMode, authSource);
|
|
401
|
+
|
|
402
|
+
// When mode is explicit, verify prerequisite env vars are present
|
|
403
|
+
if (authSource === "explicit") {
|
|
404
|
+
if (authMode === "simple-key" && !process.env.ATLAS_API_KEY) {
|
|
405
|
+
errors.push({
|
|
406
|
+
code: "MISSING_AUTH_PREREQ",
|
|
407
|
+
message:
|
|
408
|
+
"ATLAS_AUTH_MODE is set to 'api-key' but ATLAS_API_KEY is not set. " +
|
|
409
|
+
"Set ATLAS_API_KEY to a shared secret, or remove ATLAS_AUTH_MODE to use auto-detection.",
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
if (authMode === "managed" && !process.env.BETTER_AUTH_SECRET) {
|
|
413
|
+
errors.push({
|
|
414
|
+
code: "MISSING_AUTH_PREREQ",
|
|
415
|
+
message:
|
|
416
|
+
"ATLAS_AUTH_MODE is set to 'managed' but BETTER_AUTH_SECRET is not set. " +
|
|
417
|
+
"Set BETTER_AUTH_SECRET to a random string of at least 32 characters.",
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (authMode === "byot" && !process.env.ATLAS_AUTH_JWKS_URL) {
|
|
421
|
+
errors.push({
|
|
422
|
+
code: "MISSING_AUTH_PREREQ",
|
|
423
|
+
message:
|
|
424
|
+
"ATLAS_AUTH_MODE is set to 'byot' but ATLAS_AUTH_JWKS_URL is not set. " +
|
|
425
|
+
"Set ATLAS_AUTH_JWKS_URL to your identity provider's JWKS endpoint.",
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (authMode === "managed") {
|
|
431
|
+
if (!process.env.DATABASE_URL) {
|
|
432
|
+
errors.push({
|
|
433
|
+
code: "INTERNAL_DB_UNREACHABLE",
|
|
434
|
+
message:
|
|
435
|
+
"Managed auth mode requires DATABASE_URL for session storage. " +
|
|
436
|
+
"Set DATABASE_URL to a PostgreSQL connection string (postgresql://user:pass@host:5432/atlas).",
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
const secret = process.env.BETTER_AUTH_SECRET ?? "";
|
|
440
|
+
if (secret.length < 32) {
|
|
441
|
+
errors.push({
|
|
442
|
+
code: "WEAK_AUTH_SECRET",
|
|
443
|
+
message:
|
|
444
|
+
"BETTER_AUTH_SECRET is shorter than 32 characters. Use a cryptographically random string of at least 32 characters.",
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (!process.env.BETTER_AUTH_URL) {
|
|
448
|
+
const msg =
|
|
449
|
+
"BETTER_AUTH_URL is not set. Better Auth will auto-detect from the request, " +
|
|
450
|
+
"but setting it explicitly is recommended for production.";
|
|
451
|
+
if (!_startupWarnings.includes(msg)) {
|
|
452
|
+
_startupWarnings.push(msg);
|
|
453
|
+
}
|
|
454
|
+
log.warn(msg);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (authMode === "byot") {
|
|
459
|
+
const jwksUrl = process.env.ATLAS_AUTH_JWKS_URL ?? "";
|
|
460
|
+
let jwksUrlValid = false;
|
|
461
|
+
try {
|
|
462
|
+
new URL(jwksUrl);
|
|
463
|
+
jwksUrlValid = true;
|
|
464
|
+
} catch (err) {
|
|
465
|
+
errors.push({
|
|
466
|
+
code: "INVALID_JWKS_URL",
|
|
467
|
+
message:
|
|
468
|
+
`ATLAS_AUTH_JWKS_URL is not a valid URL (${err instanceof Error ? err.message : "parse error"}). Expected format: https://your-idp.com/.well-known/jwks.json`,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Reachability check — non-blocking warning since the IdP might be temporarily down
|
|
473
|
+
if (jwksUrlValid) {
|
|
474
|
+
try {
|
|
475
|
+
const resp = await fetch(jwksUrl, { signal: AbortSignal.timeout(5000) });
|
|
476
|
+
if (!resp.ok) {
|
|
477
|
+
const msg = `JWKS endpoint returned HTTP ${resp.status}. Verify the URL is correct.`;
|
|
478
|
+
log.warn({ jwksUrl, status: resp.status }, msg);
|
|
479
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
480
|
+
}
|
|
481
|
+
} catch (err) {
|
|
482
|
+
log.warn({ err: err instanceof Error ? err.message : String(err), jwksUrl }, "JWKS endpoint unreachable during startup check");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!process.env.ATLAS_AUTH_ISSUER) {
|
|
487
|
+
errors.push({
|
|
488
|
+
code: "MISSING_AUTH_ISSUER",
|
|
489
|
+
message:
|
|
490
|
+
"ATLAS_AUTH_ISSUER is required for BYOT auth mode. Set it to your identity provider's issuer URL (e.g. https://your-idp.com/).",
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Warn about orphaned auth env vars that suggest misconfiguration
|
|
496
|
+
if (authMode !== "byot" && process.env.ATLAS_AUTH_ISSUER) {
|
|
497
|
+
const msg = authSource === "explicit"
|
|
498
|
+
? `ATLAS_AUTH_ISSUER is set but auth mode is '${authMode}' (explicit). ` +
|
|
499
|
+
"Remove ATLAS_AUTH_ISSUER, or set ATLAS_AUTH_MODE=byot to use it."
|
|
500
|
+
: "ATLAS_AUTH_ISSUER is set but ATLAS_AUTH_JWKS_URL is not — BYOT auth mode is not active. " +
|
|
501
|
+
"Set ATLAS_AUTH_JWKS_URL to enable BYOT, or remove ATLAS_AUTH_ISSUER.";
|
|
502
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
503
|
+
log.warn(msg);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (authMode !== "managed" && (process.env.BETTER_AUTH_URL || process.env.BETTER_AUTH_TRUSTED_ORIGINS)) {
|
|
507
|
+
const msg = authSource === "explicit"
|
|
508
|
+
? `BETTER_AUTH_URL or BETTER_AUTH_TRUSTED_ORIGINS is set but auth mode is '${authMode}' (explicit). ` +
|
|
509
|
+
"Remove these env vars, or set ATLAS_AUTH_MODE=managed to use them."
|
|
510
|
+
: "BETTER_AUTH_URL or BETTER_AUTH_TRUSTED_ORIGINS is set but BETTER_AUTH_SECRET is not — " +
|
|
511
|
+
"managed auth mode is not active. Set BETTER_AUTH_SECRET to enable managed auth.";
|
|
512
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
513
|
+
log.warn(msg);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 7. Action framework diagnostics
|
|
517
|
+
if (process.env.ATLAS_ACTIONS_ENABLED === "true") {
|
|
518
|
+
log.info("Action framework enabled");
|
|
519
|
+
|
|
520
|
+
// Actions require authentication — reject "none" auth mode
|
|
521
|
+
if (authMode === "none") {
|
|
522
|
+
errors.push({
|
|
523
|
+
code: "ACTIONS_REQUIRE_AUTH",
|
|
524
|
+
message:
|
|
525
|
+
"Actions require authentication. Set ATLAS_API_KEY, BETTER_AUTH_SECRET, or ATLAS_AUTH_JWKS_URL to enable an auth mode.",
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Check required credentials for registered actions (warnings only —
|
|
530
|
+
// missing optional action credentials should not block chat queries)
|
|
531
|
+
try {
|
|
532
|
+
const { buildRegistry } = await import("@atlas/api/lib/tools/registry");
|
|
533
|
+
const actionRegistry = await buildRegistry({ includeActions: true });
|
|
534
|
+
const missingCreds = actionRegistry.validateActionCredentials();
|
|
535
|
+
for (const { action, missing } of missingCreds) {
|
|
536
|
+
const msg = `Action "${action}" missing credentials: ${missing.join(", ")}`;
|
|
537
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
538
|
+
log.warn(msg);
|
|
539
|
+
}
|
|
540
|
+
} catch (err) {
|
|
541
|
+
log.warn(
|
|
542
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
543
|
+
"Could not validate action credentials at startup",
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Warn if no internal DB for persistent tracking
|
|
548
|
+
if (!process.env.DATABASE_URL) {
|
|
549
|
+
const msg =
|
|
550
|
+
"Action framework requires DATABASE_URL for persistent tracking. " +
|
|
551
|
+
"Actions will use in-memory storage only (lost on restart).";
|
|
552
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
553
|
+
log.warn(msg);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Warn about high-risk actions set to auto-approve
|
|
557
|
+
try {
|
|
558
|
+
const { getConfig } = await import("@atlas/api/lib/config");
|
|
559
|
+
const config = getConfig();
|
|
560
|
+
const actionsConfig = config?.actions;
|
|
561
|
+
if (actionsConfig) {
|
|
562
|
+
const highRiskActions = ["email:send", "jira:create", "salesforce:update", "salesforce:create"];
|
|
563
|
+
for (const actionType of highRiskActions) {
|
|
564
|
+
const perAction = actionsConfig[actionType] as { approval?: string } | undefined;
|
|
565
|
+
if (perAction?.approval === "auto") {
|
|
566
|
+
const msg = `${actionType} configured for auto-approve — ensure you understand the risk`;
|
|
567
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
568
|
+
log.warn(msg);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch (err) {
|
|
573
|
+
log.warn(
|
|
574
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
575
|
+
"Could not validate action config at startup",
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// 8. Slack integration — optional, informational only
|
|
581
|
+
if (process.env.SLACK_SIGNING_SECRET) {
|
|
582
|
+
const slackMode = process.env.SLACK_CLIENT_ID ? "oauth" : "single-workspace";
|
|
583
|
+
log.info({ slackMode }, "Slack integration enabled");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// 9. Sandbox plugins — log any registered sandbox plugins before built-in pre-flight
|
|
587
|
+
try {
|
|
588
|
+
const { plugins: pluginRegistry } = await import("@atlas/api/lib/plugins/registry");
|
|
589
|
+
try {
|
|
590
|
+
const sandboxPlugins = pluginRegistry.getByType("sandbox");
|
|
591
|
+
for (const sp of sandboxPlugins) {
|
|
592
|
+
const security = (sp as { security?: Record<string, unknown> }).security;
|
|
593
|
+
log.info(
|
|
594
|
+
{
|
|
595
|
+
pluginId: sp.id,
|
|
596
|
+
version: sp.version,
|
|
597
|
+
...(security ? { security } : {}),
|
|
598
|
+
},
|
|
599
|
+
"Sandbox plugin registered: %s",
|
|
600
|
+
sp.name ?? sp.id,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
} catch (err) {
|
|
604
|
+
log.warn(
|
|
605
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
606
|
+
"Failed to enumerate sandbox plugins",
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
} catch {
|
|
610
|
+
// Plugin registry module not available — skip
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// 10. Sandbox pre-flight (explore tool isolation)
|
|
614
|
+
const isVercel = process.env.ATLAS_RUNTIME === "vercel" || !!process.env.VERCEL;
|
|
615
|
+
if (isVercel) {
|
|
616
|
+
log.info("Explore tool: Vercel sandbox active");
|
|
617
|
+
} else if (process.env.ATLAS_SANDBOX === "nsjail") {
|
|
618
|
+
// Explicit nsjail — probe and warn/error, no sidecar check
|
|
619
|
+
try {
|
|
620
|
+
const { findNsjailBinary, testNsjailCapabilities } = await import(
|
|
621
|
+
"@atlas/api/lib/tools/explore-nsjail"
|
|
622
|
+
);
|
|
623
|
+
const { markNsjailFailed } = await import(
|
|
624
|
+
"@atlas/api/lib/tools/explore"
|
|
625
|
+
);
|
|
626
|
+
const nsjailPath = findNsjailBinary();
|
|
627
|
+
if (nsjailPath) {
|
|
628
|
+
const semanticRoot = path.resolve(process.cwd(), "semantic");
|
|
629
|
+
const capResult = await testNsjailCapabilities(nsjailPath, semanticRoot);
|
|
630
|
+
if (capResult.ok) {
|
|
631
|
+
log.info("Explore tool: nsjail sandbox active");
|
|
632
|
+
} else {
|
|
633
|
+
markNsjailFailed();
|
|
634
|
+
const msg =
|
|
635
|
+
`nsjail explicitly requested (ATLAS_SANDBOX=nsjail) but namespace creation failed: ${capResult.error}. ` +
|
|
636
|
+
"This platform may not support Linux namespaces. " +
|
|
637
|
+
"Set ATLAS_SANDBOX= (empty) to allow fallback to just-bash, or check platform documentation for namespace support.";
|
|
638
|
+
log.error(msg);
|
|
639
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
640
|
+
}
|
|
641
|
+
} else {
|
|
642
|
+
const msg =
|
|
643
|
+
"ATLAS_SANDBOX=nsjail is set but nsjail binary was not found. " +
|
|
644
|
+
"Install nsjail or set ATLAS_NSJAIL_PATH to the binary location.";
|
|
645
|
+
log.error(msg);
|
|
646
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
647
|
+
}
|
|
648
|
+
} catch (err) {
|
|
649
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
650
|
+
log.warn({ err: detail }, "Sandbox pre-flight check skipped");
|
|
651
|
+
}
|
|
652
|
+
} else if (process.env.ATLAS_SANDBOX_URL) {
|
|
653
|
+
// Sidecar is the intended backend — skip nsjail entirely (no noisy warnings)
|
|
654
|
+
const sidecarUrl = process.env.ATLAS_SANDBOX_URL;
|
|
655
|
+
const { markSidecarFailed } = await import(
|
|
656
|
+
"@atlas/api/lib/tools/explore"
|
|
657
|
+
);
|
|
658
|
+
try {
|
|
659
|
+
const healthUrl = new URL("/health", sidecarUrl).toString();
|
|
660
|
+
const resp = await fetch(healthUrl, { signal: AbortSignal.timeout(5000) });
|
|
661
|
+
if (resp.ok) {
|
|
662
|
+
log.info({ url: sidecarUrl }, "Explore tool: sidecar sandbox active");
|
|
663
|
+
} else {
|
|
664
|
+
markSidecarFailed();
|
|
665
|
+
const msg =
|
|
666
|
+
`Sidecar health check returned HTTP ${resp.status} at ${sidecarUrl}. ` +
|
|
667
|
+
"Check that the sandbox-sidecar service is running and healthy.";
|
|
668
|
+
log.error(msg);
|
|
669
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
670
|
+
}
|
|
671
|
+
} catch (err) {
|
|
672
|
+
markSidecarFailed();
|
|
673
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
674
|
+
const msg =
|
|
675
|
+
`Sidecar unreachable at ${sidecarUrl}: ${detail}. ` +
|
|
676
|
+
"The sidecar may not be running yet — explore will retry on first use.";
|
|
677
|
+
log.error(msg);
|
|
678
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
679
|
+
}
|
|
680
|
+
} else {
|
|
681
|
+
// Auto-detect nsjail, fall back to just-bash
|
|
682
|
+
let nsjailActive = false;
|
|
683
|
+
try {
|
|
684
|
+
const { findNsjailBinary, testNsjailCapabilities } = await import(
|
|
685
|
+
"@atlas/api/lib/tools/explore-nsjail"
|
|
686
|
+
);
|
|
687
|
+
const { markNsjailFailed } = await import(
|
|
688
|
+
"@atlas/api/lib/tools/explore"
|
|
689
|
+
);
|
|
690
|
+
const nsjailPath = findNsjailBinary();
|
|
691
|
+
if (nsjailPath) {
|
|
692
|
+
const semanticRoot = path.resolve(process.cwd(), "semantic");
|
|
693
|
+
const capResult = await testNsjailCapabilities(nsjailPath, semanticRoot);
|
|
694
|
+
if (capResult.ok) {
|
|
695
|
+
log.info("Explore tool: nsjail sandbox active");
|
|
696
|
+
nsjailActive = true;
|
|
697
|
+
} else {
|
|
698
|
+
markNsjailFailed();
|
|
699
|
+
const msg =
|
|
700
|
+
`nsjail available but namespace creation failed: ${capResult.error} — ` +
|
|
701
|
+
"falling back to just-bash (no process isolation).";
|
|
702
|
+
log.warn(msg);
|
|
703
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} catch (err) {
|
|
707
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
708
|
+
log.warn({ err: detail }, "Sandbox pre-flight check skipped");
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (!nsjailActive) {
|
|
712
|
+
log.info(
|
|
713
|
+
"Explore tool: just-bash (no process isolation). Install nsjail or configure ATLAS_SANDBOX_URL for sandboxed execution.",
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
_cached = errors;
|
|
719
|
+
_cachedAt = Date.now();
|
|
720
|
+
return errors;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function isValidUrl(url: string): boolean {
|
|
724
|
+
try {
|
|
725
|
+
new URL(url);
|
|
726
|
+
return true;
|
|
727
|
+
} catch {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
}
|