@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,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack integration routes.
|
|
3
|
+
*
|
|
4
|
+
* - POST /api/slack/commands — slash command handler (/atlas)
|
|
5
|
+
* - POST /api/slack/events — Events API (thread follow-ups, url_verification)
|
|
6
|
+
* - GET /api/slack/install — OAuth install redirect
|
|
7
|
+
* - GET /api/slack/callback — OAuth callback
|
|
8
|
+
*
|
|
9
|
+
* POST routes verify Slack request signatures. OAuth routes use Slack's
|
|
10
|
+
* server-to-server code exchange. The slash command acks within 3 seconds
|
|
11
|
+
* and processes the query asynchronously.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Hono } from "hono";
|
|
15
|
+
import { executeAgentQuery } from "@atlas/api/lib/agent-query";
|
|
16
|
+
import { createLogger } from "@atlas/api/lib/logger";
|
|
17
|
+
import { checkRateLimit } from "@atlas/api/lib/auth/middleware";
|
|
18
|
+
import { verifySlackSignature } from "@atlas/api/lib/slack/verify";
|
|
19
|
+
import { postMessage, updateMessage, postEphemeral, slackAPI } from "@atlas/api/lib/slack/api";
|
|
20
|
+
import { formatQueryResponse, formatErrorResponse, formatActionApproval, formatActionResult } from "@atlas/api/lib/slack/format";
|
|
21
|
+
import { approveAction, denyAction, getAction } from "@atlas/api/lib/tools/actions/handler";
|
|
22
|
+
import { getBotToken, saveInstallation } from "@atlas/api/lib/slack/store";
|
|
23
|
+
import { getConversationId, setConversationId } from "@atlas/api/lib/slack/threads";
|
|
24
|
+
import { createConversation, addMessage, getConversation, generateTitle } from "@atlas/api/lib/conversations";
|
|
25
|
+
import { SENSITIVE_PATTERNS } from "@atlas/api/lib/security";
|
|
26
|
+
|
|
27
|
+
const log = createLogger("slack");
|
|
28
|
+
|
|
29
|
+
const slack = new Hono();
|
|
30
|
+
|
|
31
|
+
// --- Verify Slack signature ---
|
|
32
|
+
|
|
33
|
+
async function verifyRequest(
|
|
34
|
+
c: { req: { raw: Request; header: (name: string) => string | undefined } },
|
|
35
|
+
): Promise<{ valid: boolean; body: string }> {
|
|
36
|
+
const signingSecret = process.env.SLACK_SIGNING_SECRET;
|
|
37
|
+
if (!signingSecret) {
|
|
38
|
+
log.error("SLACK_SIGNING_SECRET not set — all Slack requests will be rejected");
|
|
39
|
+
return { valid: false, body: "" };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const body = await c.req.raw.clone().text();
|
|
43
|
+
const signature = c.req.header("x-slack-signature") ?? null;
|
|
44
|
+
const timestamp = c.req.header("x-slack-request-timestamp") ?? null;
|
|
45
|
+
|
|
46
|
+
const result = verifySlackSignature(signingSecret, signature, timestamp, body);
|
|
47
|
+
if (!result.valid) {
|
|
48
|
+
log.warn({ error: result.error }, "Slack signature verification failed");
|
|
49
|
+
}
|
|
50
|
+
return { valid: result.valid, body };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Scrub sensitive info from error messages before sending to Slack.
|
|
55
|
+
*/
|
|
56
|
+
function scrubError(message: string): string {
|
|
57
|
+
if (SENSITIVE_PATTERNS.test(message) || message.length > 200) {
|
|
58
|
+
return "An internal error occurred. Check server logs for details.";
|
|
59
|
+
}
|
|
60
|
+
return message;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// --- POST /api/slack/commands ---
|
|
64
|
+
|
|
65
|
+
slack.post("/commands", async (c) => {
|
|
66
|
+
const { valid, body } = await verifyRequest(c);
|
|
67
|
+
if (!valid) {
|
|
68
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const params = new URLSearchParams(body);
|
|
72
|
+
const text = params.get("text") ?? "";
|
|
73
|
+
const channelId = params.get("channel_id") ?? "";
|
|
74
|
+
const userId = params.get("user_id") ?? "";
|
|
75
|
+
const teamId = params.get("team_id") ?? "";
|
|
76
|
+
const responseUrl = params.get("response_url") ?? "";
|
|
77
|
+
|
|
78
|
+
if (!text.trim()) {
|
|
79
|
+
return c.json({
|
|
80
|
+
response_type: "ephemeral",
|
|
81
|
+
text: "Usage: `/atlas <your question>`\nExample: `/atlas how many active users last month?`",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
log.info({ channelId, userId, teamId, question: text.slice(0, 100) }, "Slash command received");
|
|
86
|
+
|
|
87
|
+
// Ack immediately — Slack requires response within 3 seconds
|
|
88
|
+
// Fire off async processing
|
|
89
|
+
const processAsync = async () => {
|
|
90
|
+
try {
|
|
91
|
+
const token = await getBotToken(teamId);
|
|
92
|
+
if (!token) {
|
|
93
|
+
log.error({ teamId }, "No bot token available for team");
|
|
94
|
+
if (responseUrl) {
|
|
95
|
+
try {
|
|
96
|
+
await fetch(responseUrl, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({ response_type: "ephemeral", text: "Atlas is not configured for this workspace. Ask an admin to install via /api/slack/install." }),
|
|
100
|
+
signal: AbortSignal.timeout(10_000),
|
|
101
|
+
});
|
|
102
|
+
} catch (urlErr) {
|
|
103
|
+
log.error({ err: urlErr instanceof Error ? urlErr.message : String(urlErr) }, "Failed to send response_url fallback");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const rateCheck = checkRateLimit(`slack:${teamId}:${userId}`);
|
|
110
|
+
if (!rateCheck.allowed) {
|
|
111
|
+
await postMessage(token, { channel: channelId, text: "Rate limit exceeded. Please wait before trying again." });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Post initial "Thinking..." message to get a thread_ts
|
|
116
|
+
const thinkingResult = await postMessage(token, {
|
|
117
|
+
channel: channelId,
|
|
118
|
+
text: `:hourglass_flowing_sand: Thinking about: _${text.slice(0, 150)}_...`,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!thinkingResult.ok || !thinkingResult.ts) {
|
|
122
|
+
log.error({ error: thinkingResult.error }, "Failed to post thinking message");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const messageTs = thinkingResult.ts;
|
|
127
|
+
|
|
128
|
+
// Look up or create conversation mapping
|
|
129
|
+
const existingConversationId = await getConversationId(channelId, messageTs);
|
|
130
|
+
const conversationId = existingConversationId ?? crypto.randomUUID();
|
|
131
|
+
|
|
132
|
+
if (!existingConversationId) {
|
|
133
|
+
setConversationId(channelId, messageTs, conversationId);
|
|
134
|
+
// Persist conversation so thread follow-ups can load history
|
|
135
|
+
createConversation({
|
|
136
|
+
id: conversationId,
|
|
137
|
+
title: generateTitle(text),
|
|
138
|
+
surface: "slack",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const queryResult = await executeAgentQuery(text);
|
|
143
|
+
|
|
144
|
+
// Persist messages for thread history
|
|
145
|
+
addMessage({ conversationId, role: "user", content: text });
|
|
146
|
+
addMessage({ conversationId, role: "assistant", content: queryResult.answer });
|
|
147
|
+
|
|
148
|
+
const blocks = formatQueryResponse(queryResult);
|
|
149
|
+
const updateResult = await updateMessage(token, {
|
|
150
|
+
channel: channelId,
|
|
151
|
+
ts: messageTs,
|
|
152
|
+
text: queryResult.answer,
|
|
153
|
+
blocks,
|
|
154
|
+
});
|
|
155
|
+
if (!updateResult.ok) {
|
|
156
|
+
log.error({ error: updateResult.error, channel: channelId, ts: messageTs }, "Failed to update Slack message with query result");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Post ephemeral approval prompts for pending actions
|
|
160
|
+
if (queryResult.pendingActions?.length) {
|
|
161
|
+
for (const action of queryResult.pendingActions) {
|
|
162
|
+
const approvalBlocks = formatActionApproval(action);
|
|
163
|
+
const ephResult = await postEphemeral(token, {
|
|
164
|
+
channel: channelId,
|
|
165
|
+
user: userId,
|
|
166
|
+
text: `Action requires approval: ${action.summary}`,
|
|
167
|
+
blocks: approvalBlocks,
|
|
168
|
+
thread_ts: messageTs,
|
|
169
|
+
});
|
|
170
|
+
if (!ephResult.ok) {
|
|
171
|
+
log.error({ error: ephResult.error, channel: channelId, userId, actionId: action.id }, "Failed to post ephemeral action approval prompt");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
log.error(
|
|
177
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
178
|
+
"Slack async command processing failed",
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Try to post error message if we can
|
|
182
|
+
try {
|
|
183
|
+
const token = await getBotToken(teamId);
|
|
184
|
+
if (token) {
|
|
185
|
+
const errorMessage = scrubError(
|
|
186
|
+
err instanceof Error ? err.message : "Unknown error",
|
|
187
|
+
);
|
|
188
|
+
await postMessage(token, {
|
|
189
|
+
channel: channelId,
|
|
190
|
+
text: errorMessage,
|
|
191
|
+
blocks: formatErrorResponse(errorMessage),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
} catch (innerErr) {
|
|
195
|
+
log.error({ err: innerErr instanceof Error ? innerErr.message : String(innerErr) }, "Failed to send error message to Slack");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Fire-and-forget with error logging
|
|
201
|
+
processAsync().catch((err) => {
|
|
202
|
+
log.error(
|
|
203
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
204
|
+
"Unhandled error in async Slack processing",
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Immediate ack to Slack
|
|
209
|
+
return c.json({
|
|
210
|
+
response_type: "in_channel",
|
|
211
|
+
text: `:hourglass_flowing_sand: Processing your question...`,
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// --- POST /api/slack/events ---
|
|
216
|
+
|
|
217
|
+
slack.post("/events", async (c) => {
|
|
218
|
+
const { valid, body } = await verifyRequest(c);
|
|
219
|
+
|
|
220
|
+
let payload: Record<string, unknown>;
|
|
221
|
+
try {
|
|
222
|
+
payload = JSON.parse(body);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Slack events received non-JSON body");
|
|
225
|
+
return c.json({ error: "Invalid JSON" }, 400);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!valid) {
|
|
229
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Handle url_verification challenge (signature verified above)
|
|
233
|
+
if (payload.type === "url_verification") {
|
|
234
|
+
return c.json({ challenge: payload.challenge });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (payload.type === "event_callback") {
|
|
238
|
+
const event = payload.event as Record<string, unknown> | undefined;
|
|
239
|
+
if (!event) {
|
|
240
|
+
return c.json({ ok: true });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Ignore bot messages to prevent loops
|
|
244
|
+
if (event.bot_id) {
|
|
245
|
+
return c.json({ ok: true });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const eventType = event.type as string;
|
|
249
|
+
const text = (event.text as string) ?? "";
|
|
250
|
+
const channel = (event.channel as string) ?? "";
|
|
251
|
+
const threadTs = (event.thread_ts as string) ?? "";
|
|
252
|
+
const teamId = (payload.team_id as string) ?? "";
|
|
253
|
+
|
|
254
|
+
// Only handle messages in threads (follow-up questions)
|
|
255
|
+
if (eventType === "message" && threadTs && text.trim()) {
|
|
256
|
+
log.info(
|
|
257
|
+
{ channel, threadTs, question: text.slice(0, 100) },
|
|
258
|
+
"Thread follow-up received",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Process async — ack immediately
|
|
262
|
+
const processAsync = async () => {
|
|
263
|
+
try {
|
|
264
|
+
const token = await getBotToken(teamId);
|
|
265
|
+
if (!token) {
|
|
266
|
+
log.error({ teamId }, "No bot token for thread follow-up");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const rateCheck = checkRateLimit(`slack:${teamId}`);
|
|
271
|
+
if (!rateCheck.allowed) {
|
|
272
|
+
await postMessage(token, { channel, text: "Rate limit exceeded. Please wait before trying again.", thread_ts: threadTs });
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check for existing conversation mapping
|
|
277
|
+
const conversationId = await getConversationId(channel, threadTs);
|
|
278
|
+
|
|
279
|
+
// Load conversation history for multi-turn context
|
|
280
|
+
let priorMessages: Array<{ role: "user" | "assistant"; content: string }> | undefined;
|
|
281
|
+
if (conversationId) {
|
|
282
|
+
log.debug({ conversationId, threadTs }, "Found existing conversation for thread");
|
|
283
|
+
const result = await getConversation(conversationId);
|
|
284
|
+
if (result.ok && result.data.messages.length) {
|
|
285
|
+
priorMessages = result.data.messages
|
|
286
|
+
.filter((m): m is typeof m & { role: "user" | "assistant" } =>
|
|
287
|
+
m.role === "user" || m.role === "assistant",
|
|
288
|
+
)
|
|
289
|
+
.map((m) => ({
|
|
290
|
+
role: m.role,
|
|
291
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
|
|
292
|
+
}));
|
|
293
|
+
} else if (!result.ok && result.reason === "error") {
|
|
294
|
+
log.warn({ conversationId, threadTs }, "Failed to load conversation history — proceeding without context");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const queryResult = await executeAgentQuery(text, undefined, priorMessages ? { priorMessages } : undefined);
|
|
299
|
+
|
|
300
|
+
// Persist new messages for future follow-ups
|
|
301
|
+
if (conversationId) {
|
|
302
|
+
addMessage({ conversationId, role: "user", content: text });
|
|
303
|
+
addMessage({ conversationId, role: "assistant", content: queryResult.answer });
|
|
304
|
+
}
|
|
305
|
+
const blocks = formatQueryResponse(queryResult);
|
|
306
|
+
|
|
307
|
+
const postResult = await postMessage(token, {
|
|
308
|
+
channel,
|
|
309
|
+
text: queryResult.answer,
|
|
310
|
+
blocks,
|
|
311
|
+
thread_ts: threadTs,
|
|
312
|
+
});
|
|
313
|
+
if (!postResult.ok) {
|
|
314
|
+
log.error({ error: postResult.error, channel, threadTs }, "Failed to post thread follow-up response");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Post ephemeral approval prompts for pending actions
|
|
318
|
+
const eventUserId = (event.user as string) ?? "";
|
|
319
|
+
if (queryResult.pendingActions?.length && eventUserId) {
|
|
320
|
+
for (const action of queryResult.pendingActions) {
|
|
321
|
+
const approvalBlocks = formatActionApproval(action);
|
|
322
|
+
const ephResult = await postEphemeral(token, {
|
|
323
|
+
channel,
|
|
324
|
+
user: eventUserId,
|
|
325
|
+
text: `Action requires approval: ${action.summary}`,
|
|
326
|
+
blocks: approvalBlocks,
|
|
327
|
+
thread_ts: threadTs,
|
|
328
|
+
});
|
|
329
|
+
if (!ephResult.ok) {
|
|
330
|
+
log.error({ error: ephResult.error, channel, userId: eventUserId, actionId: action.id }, "Failed to post ephemeral action approval prompt");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} catch (err) {
|
|
335
|
+
log.error(
|
|
336
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
337
|
+
"Thread follow-up processing failed",
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const token = await getBotToken(teamId);
|
|
342
|
+
if (token) {
|
|
343
|
+
const errorMessage = scrubError(
|
|
344
|
+
err instanceof Error ? err.message : "Unknown error",
|
|
345
|
+
);
|
|
346
|
+
await postMessage(token, {
|
|
347
|
+
channel,
|
|
348
|
+
text: errorMessage,
|
|
349
|
+
blocks: formatErrorResponse(errorMessage),
|
|
350
|
+
thread_ts: threadTs,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
} catch (innerErr) {
|
|
354
|
+
log.error({ err: innerErr instanceof Error ? innerErr.message : String(innerErr) }, "Failed to send thread error message");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
processAsync().catch((err) => {
|
|
360
|
+
log.error(
|
|
361
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
362
|
+
"Unhandled error in thread processing",
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return c.json({ ok: true });
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// --- POST /api/slack/interactions ---
|
|
372
|
+
|
|
373
|
+
slack.post("/interactions", async (c) => {
|
|
374
|
+
const { valid, body } = await verifyRequest(c);
|
|
375
|
+
if (!valid) {
|
|
376
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Slack sends interactions as URL-encoded form with a "payload" JSON field
|
|
380
|
+
const params = new URLSearchParams(body);
|
|
381
|
+
const payloadStr = params.get("payload");
|
|
382
|
+
if (!payloadStr) {
|
|
383
|
+
return c.json({ error: "Missing payload" }, 400);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let payload: Record<string, unknown>;
|
|
387
|
+
try {
|
|
388
|
+
payload = JSON.parse(payloadStr);
|
|
389
|
+
} catch {
|
|
390
|
+
return c.json({ error: "Invalid payload JSON" }, 400);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (payload.type !== "block_actions") {
|
|
394
|
+
log.debug({ type: payload.type }, "Acked non-block_actions Slack interaction type");
|
|
395
|
+
return c.json({ ok: true });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const actions = payload.actions as Array<{
|
|
399
|
+
action_id: string;
|
|
400
|
+
value: string;
|
|
401
|
+
}> | undefined;
|
|
402
|
+
|
|
403
|
+
if (!actions?.length) {
|
|
404
|
+
return c.json({ ok: true });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const responseUrl = (payload.response_url as string) ?? "";
|
|
408
|
+
const userId = ((payload.user as Record<string, unknown>)?.id as string) ?? "";
|
|
409
|
+
|
|
410
|
+
// Ack immediately — process asynchronously
|
|
411
|
+
const processAsync = async () => {
|
|
412
|
+
for (const act of actions) {
|
|
413
|
+
const actionId = act.value;
|
|
414
|
+
const isApprove = act.action_id === "atlas_action_approve";
|
|
415
|
+
const isDeny = act.action_id === "atlas_action_deny";
|
|
416
|
+
|
|
417
|
+
if (!isApprove && !isDeny) {
|
|
418
|
+
if (typeof act.action_id === "string" && act.action_id.startsWith("atlas_")) {
|
|
419
|
+
log.warn({ actionId: act.action_id }, "Unrecognized Atlas action_id in Slack interaction");
|
|
420
|
+
}
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const actionEntry = await getAction(actionId);
|
|
426
|
+
if (!actionEntry) {
|
|
427
|
+
log.warn({ actionId, userId }, "Slack interaction for unknown action");
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const pendingAction = {
|
|
432
|
+
id: actionEntry.id,
|
|
433
|
+
type: actionEntry.action_type,
|
|
434
|
+
target: actionEntry.target,
|
|
435
|
+
summary: actionEntry.summary,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
if (isApprove) {
|
|
439
|
+
const result = await approveAction(actionId, `slack:${userId}`);
|
|
440
|
+
if (!result) {
|
|
441
|
+
log.warn({ actionId, userId }, "Action already resolved when approve attempted");
|
|
442
|
+
if (responseUrl) {
|
|
443
|
+
const resp = await fetch(responseUrl, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: { "Content-Type": "application/json" },
|
|
446
|
+
body: JSON.stringify({
|
|
447
|
+
replace_original: true,
|
|
448
|
+
text: "This action has already been resolved.",
|
|
449
|
+
}),
|
|
450
|
+
signal: AbortSignal.timeout(10_000),
|
|
451
|
+
});
|
|
452
|
+
if (!resp.ok) {
|
|
453
|
+
log.warn({ actionId, status: resp.status }, "Slack response_url returned non-OK status");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const status = result.status === "executed" ? "executed" : result.status === "failed" ? "failed" : "approved";
|
|
459
|
+
const resultBlocks = formatActionResult(pendingAction, status, result.error ?? undefined);
|
|
460
|
+
|
|
461
|
+
if (responseUrl) {
|
|
462
|
+
const resp = await fetch(responseUrl, {
|
|
463
|
+
method: "POST",
|
|
464
|
+
headers: { "Content-Type": "application/json" },
|
|
465
|
+
body: JSON.stringify({ replace_original: true, blocks: resultBlocks }),
|
|
466
|
+
signal: AbortSignal.timeout(10_000),
|
|
467
|
+
});
|
|
468
|
+
if (!resp.ok) {
|
|
469
|
+
log.warn({ actionId, status: resp.status }, "Slack response_url returned non-OK status");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
const result = await denyAction(actionId, `slack:${userId}`);
|
|
474
|
+
if (!result) {
|
|
475
|
+
log.warn({ actionId }, "Action already resolved when deny attempted");
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const resultBlocks = formatActionResult(pendingAction, "denied");
|
|
479
|
+
|
|
480
|
+
if (responseUrl) {
|
|
481
|
+
const resp = await fetch(responseUrl, {
|
|
482
|
+
method: "POST",
|
|
483
|
+
headers: { "Content-Type": "application/json" },
|
|
484
|
+
body: JSON.stringify({ replace_original: true, blocks: resultBlocks }),
|
|
485
|
+
signal: AbortSignal.timeout(10_000),
|
|
486
|
+
});
|
|
487
|
+
if (!resp.ok) {
|
|
488
|
+
log.warn({ actionId, status: resp.status }, "Slack response_url returned non-OK status");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
} catch (err) {
|
|
493
|
+
log.error(
|
|
494
|
+
{ err: err instanceof Error ? err : new Error(String(err)), actionId },
|
|
495
|
+
"Failed to process Slack action interaction",
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
if (responseUrl) {
|
|
499
|
+
try {
|
|
500
|
+
const resp = await fetch(responseUrl, {
|
|
501
|
+
method: "POST",
|
|
502
|
+
headers: { "Content-Type": "application/json" },
|
|
503
|
+
body: JSON.stringify({
|
|
504
|
+
replace_original: true,
|
|
505
|
+
text: ":warning: Failed to process action. Please try again or use the web UI.",
|
|
506
|
+
}),
|
|
507
|
+
signal: AbortSignal.timeout(10_000),
|
|
508
|
+
});
|
|
509
|
+
if (!resp.ok) {
|
|
510
|
+
log.warn({ status: resp.status }, "Slack response_url returned non-OK status for error message");
|
|
511
|
+
}
|
|
512
|
+
} catch (innerErr) {
|
|
513
|
+
log.error({ err: innerErr instanceof Error ? innerErr.message : String(innerErr) }, "Failed to send error via response_url");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
processAsync().catch((err) => {
|
|
521
|
+
log.error(
|
|
522
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
523
|
+
"Unhandled error in Slack interaction processing",
|
|
524
|
+
);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
return c.json({ ok: true });
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// --- OAuth CSRF state ---
|
|
531
|
+
|
|
532
|
+
const pendingOAuthStates = new Map<string, number>();
|
|
533
|
+
setInterval(() => {
|
|
534
|
+
const now = Date.now();
|
|
535
|
+
for (const [state, expiry] of pendingOAuthStates) {
|
|
536
|
+
if (now > expiry) pendingOAuthStates.delete(state);
|
|
537
|
+
}
|
|
538
|
+
}, 600_000).unref();
|
|
539
|
+
|
|
540
|
+
// --- GET /api/slack/install ---
|
|
541
|
+
|
|
542
|
+
slack.get("/install", (c) => {
|
|
543
|
+
const clientId = process.env.SLACK_CLIENT_ID;
|
|
544
|
+
if (!clientId) {
|
|
545
|
+
return c.json({ error: "OAuth not configured" }, 501);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const state = crypto.randomUUID();
|
|
549
|
+
pendingOAuthStates.set(state, Date.now() + 600_000);
|
|
550
|
+
|
|
551
|
+
const scopes = "commands,chat:write,app_mentions:read";
|
|
552
|
+
const url = `https://slack.com/oauth/v2/authorize?client_id=${clientId}&scope=${scopes}&state=${state}`;
|
|
553
|
+
return c.redirect(url);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// --- GET /api/slack/callback ---
|
|
557
|
+
|
|
558
|
+
slack.get("/callback", async (c) => {
|
|
559
|
+
const clientId = process.env.SLACK_CLIENT_ID;
|
|
560
|
+
const clientSecret = process.env.SLACK_CLIENT_SECRET;
|
|
561
|
+
|
|
562
|
+
if (!clientId || !clientSecret) {
|
|
563
|
+
return c.json({ error: "OAuth not configured" }, 501);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const state = c.req.query("state");
|
|
567
|
+
if (!state || !pendingOAuthStates.has(state)) {
|
|
568
|
+
return c.json({ error: "Invalid or expired state parameter" }, 400);
|
|
569
|
+
}
|
|
570
|
+
pendingOAuthStates.delete(state);
|
|
571
|
+
|
|
572
|
+
const code = c.req.query("code");
|
|
573
|
+
if (!code) {
|
|
574
|
+
return c.json({ error: "Missing code parameter" }, 400);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const result = await slackAPI("oauth.v2.access", "", {
|
|
578
|
+
client_id: clientId,
|
|
579
|
+
client_secret: clientSecret,
|
|
580
|
+
code,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (!result.ok) {
|
|
584
|
+
log.error({ error: result.error }, "OAuth exchange failed");
|
|
585
|
+
return c.json({ error: "OAuth failed" }, 400);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const data = result as unknown as Record<string, unknown>;
|
|
589
|
+
const team = data.team as { id?: string } | undefined;
|
|
590
|
+
const accessToken = (data.access_token as string) ?? "";
|
|
591
|
+
const teamId = team?.id ?? "";
|
|
592
|
+
|
|
593
|
+
if (teamId && accessToken) {
|
|
594
|
+
try {
|
|
595
|
+
await saveInstallation(teamId, accessToken);
|
|
596
|
+
log.info({ teamId }, "Slack installation saved");
|
|
597
|
+
} catch (saveErr) {
|
|
598
|
+
log.error({ err: saveErr instanceof Error ? saveErr.message : String(saveErr), teamId }, "Failed to save Slack installation");
|
|
599
|
+
return c.html("<html><body><h1>Installation Failed</h1><p>Could not save the installation. Please try again.</p></body></html>", 500);
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
log.error({ hasTeamId: !!teamId, hasAccessToken: !!accessToken }, "OAuth response missing team_id or access_token");
|
|
603
|
+
return c.html("<html><body><h1>Installation Failed</h1><p>The OAuth response was incomplete. Please try again.</p></body></html>", 500);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return c.html(
|
|
607
|
+
"<html><body><h1>Atlas installed!</h1><p>You can now use /atlas in your Slack workspace.</p></body></html>",
|
|
608
|
+
);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
export { slack };
|