slackhive 0.1.37 → 0.1.39
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/.dockerignore +14 -0
- package/.env.example +44 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +65 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +27 -0
- package/.github/dependabot.yml +20 -0
- package/.github/workflows/audit.yml +149 -0
- package/.github/workflows/ci.yml +135 -0
- package/CHANGELOG.md +52 -0
- package/CODE_OF_CONDUCT.md +37 -0
- package/CONTRIBUTING.md +204 -0
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/SECURITY.md +47 -0
- package/apps/runner/Dockerfile +33 -0
- package/apps/runner/dist/__tests__/channel-restrictions.test.d.ts +8 -0
- package/apps/runner/dist/__tests__/channel-restrictions.test.js +63 -0
- package/apps/runner/dist/__tests__/channel-restrictions.test.js.map +1 -0
- package/apps/runner/dist/__tests__/claude-handler-resolve.test.d.ts +20 -0
- package/apps/runner/dist/__tests__/claude-handler-resolve.test.js +178 -0
- package/apps/runner/dist/__tests__/claude-handler-resolve.test.js.map +1 -0
- package/apps/runner/dist/__tests__/compile-claude-md.test.d.ts +13 -0
- package/apps/runner/dist/__tests__/compile-claude-md.test.js +144 -0
- package/apps/runner/dist/__tests__/compile-claude-md.test.js.map +1 -0
- package/apps/runner/dist/__tests__/memory-sync.test.d.ts +11 -0
- package/apps/runner/dist/__tests__/memory-sync.test.js +56 -0
- package/apps/runner/dist/__tests__/memory-sync.test.js.map +1 -0
- package/apps/runner/dist/__tests__/slack-file-support.test.d.ts +9 -0
- package/apps/runner/dist/__tests__/slack-file-support.test.js +271 -0
- package/apps/runner/dist/__tests__/slack-file-support.test.js.map +1 -0
- package/apps/runner/dist/__tests__/slack-formatting.test.d.ts +12 -0
- package/apps/runner/dist/__tests__/slack-formatting.test.js +400 -0
- package/apps/runner/dist/__tests__/slack-formatting.test.js.map +1 -0
- package/apps/runner/dist/__tests__/thread-context.test.d.ts +12 -0
- package/apps/runner/dist/__tests__/thread-context.test.js +182 -0
- package/apps/runner/dist/__tests__/thread-context.test.js.map +1 -0
- package/apps/runner/dist/agent-runner.d.ts +118 -0
- package/apps/runner/dist/agent-runner.js +352 -0
- package/apps/runner/dist/agent-runner.js.map +1 -0
- package/apps/runner/dist/claude-handler.d.ts +122 -0
- package/apps/runner/dist/claude-handler.js +402 -0
- package/apps/runner/dist/claude-handler.js.map +1 -0
- package/apps/runner/dist/compile-claude-md.d.ts +59 -0
- package/apps/runner/dist/compile-claude-md.js +291 -0
- package/apps/runner/dist/compile-claude-md.js.map +1 -0
- package/apps/runner/dist/correction-handler.d.ts +46 -0
- package/apps/runner/dist/correction-handler.js +162 -0
- package/apps/runner/dist/correction-handler.js.map +1 -0
- package/apps/runner/dist/correction-manager.d.ts +53 -0
- package/apps/runner/dist/correction-manager.js +241 -0
- package/apps/runner/dist/correction-manager.js.map +1 -0
- package/apps/runner/dist/db.d.ts +193 -0
- package/apps/runner/dist/db.js +492 -0
- package/apps/runner/dist/db.js.map +1 -0
- package/apps/runner/dist/index.d.ts +9 -0
- package/apps/runner/dist/index.js +43 -0
- package/apps/runner/dist/index.js.map +1 -0
- package/apps/runner/dist/job-scheduler.d.ts +57 -0
- package/apps/runner/dist/job-scheduler.js +150 -0
- package/apps/runner/dist/job-scheduler.js.map +1 -0
- package/apps/runner/dist/logger.d.ts +32 -0
- package/apps/runner/dist/logger.js +52 -0
- package/apps/runner/dist/logger.js.map +1 -0
- package/apps/runner/dist/mcp-process-manager.d.ts +38 -0
- package/apps/runner/dist/mcp-process-manager.js +189 -0
- package/apps/runner/dist/mcp-process-manager.js.map +1 -0
- package/apps/runner/dist/memory-mcp.d.ts +14 -0
- package/apps/runner/dist/memory-mcp.js +88 -0
- package/apps/runner/dist/memory-mcp.js.map +1 -0
- package/apps/runner/dist/memory-watcher.d.ts +78 -0
- package/apps/runner/dist/memory-watcher.js +220 -0
- package/apps/runner/dist/memory-watcher.js.map +1 -0
- package/apps/runner/dist/slack-handler.d.ts +120 -0
- package/apps/runner/dist/slack-handler.js +843 -0
- package/apps/runner/dist/slack-handler.js.map +1 -0
- package/apps/runner/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/apps/runner/package.json +42 -0
- package/apps/runner/src/__tests__/channel-restrictions.test.ts +75 -0
- package/apps/runner/src/__tests__/claude-handler-resolve.test.ts +160 -0
- package/apps/runner/src/__tests__/compile-claude-md.test.ts +139 -0
- package/apps/runner/src/__tests__/memory-sync.test.ts +59 -0
- package/apps/runner/src/__tests__/slack-file-support.test.ts +376 -0
- package/apps/runner/src/__tests__/slack-formatting.test.ts +495 -0
- package/apps/runner/src/__tests__/thread-context.test.ts +215 -0
- package/apps/runner/src/agent-runner.ts +397 -0
- package/apps/runner/src/claude-handler.ts +475 -0
- package/apps/runner/src/compile-claude-md.ts +283 -0
- package/apps/runner/src/correction-handler.ts +191 -0
- package/apps/runner/src/correction-manager.ts +285 -0
- package/apps/runner/src/db.ts +604 -0
- package/apps/runner/src/index.ts +46 -0
- package/apps/runner/src/job-scheduler.ts +165 -0
- package/apps/runner/src/logger.ts +49 -0
- package/apps/runner/src/mcp-process-manager.ts +195 -0
- package/apps/runner/src/memory-mcp.ts +85 -0
- package/apps/runner/src/memory-watcher.ts +215 -0
- package/apps/runner/src/slack-handler.ts +929 -0
- package/apps/runner/tsconfig.json +17 -0
- package/apps/runner/vitest.config.mts +17 -0
- package/apps/web/.eslintrc.json +3 -0
- package/apps/web/.next/app-build-manifest.json +323 -0
- package/apps/web/.next/app-path-routes-manifest.json +46 -0
- package/apps/web/.next/build-manifest.json +33 -0
- package/apps/web/.next/cache/.previewinfo +1 -0
- package/apps/web/.next/cache/.rscinfo +1 -0
- package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/1.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/2.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/3.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/4.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/1.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/1.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/2.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/apps/web/.next/diagnostics/build-diagnostics.json +6 -0
- package/apps/web/.next/diagnostics/framework.json +1 -0
- package/apps/web/.next/package.json +1 -0
- package/apps/web/.next/react-loadable-manifest.json +1 -0
- package/apps/web/.next/server/app/_not-found/page.js +2 -0
- package/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/agents/[slug]/page.js +4 -0
- package/apps/web/.next/server/app/agents/[slug]/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/agents/[slug]/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/agents/new/page.js +2 -0
- package/apps/web/.next/server/app/agents/new/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/agents/new/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/access/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/access/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/access/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js +6 -0
- package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/claude-md/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/logs/route.js +3 -0
- package/apps/web/.next/server/app/api/agents/[id]/logs/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/logs/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/manifest/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/mcps/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/permissions/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/reload/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/reload/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/reload/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/restrictions/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/route.js +33 -0
- package/apps/web/.next/server/app/api/agents/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/slack-info/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/start/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/start/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/start/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/stop/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/stop/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/stop/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/route.js +91 -0
- package/apps/web/.next/server/app/api/agents/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/login/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/login/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/logout/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/logout/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/me/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/me/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/me/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/[id]/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/users/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/users/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/[key]/route.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/[key]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/env-vars/[key]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/route.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/env-vars/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/route.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/runs/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/jobs/route.js +1 -0
- package/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/jobs/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/route.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/test/route.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/test/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/test/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/mcps/route.js +1 -0
- package/apps/web/.next/server/app/api/mcps/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/mcps/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/settings/route.js +1 -0
- package/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/icon.svg/route.js +1 -0
- package/apps/web/.next/server/app/icon.svg/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/jobs/page.js +2 -0
- package/apps/web/.next/server/app/jobs/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/jobs/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/login/page.js +2 -0
- package/apps/web/.next/server/app/login/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/login/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/page.js +2 -0
- package/apps/web/.next/server/app/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/settings/env-vars/page.js +2 -0
- package/apps/web/.next/server/app/settings/env-vars/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/settings/env-vars/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/settings/mcps/page.js +2 -0
- package/apps/web/.next/server/app/settings/mcps/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/settings/mcps/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/settings/page.js +2 -0
- package/apps/web/.next/server/app/settings/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app-paths-manifest.json +46 -0
- package/apps/web/.next/server/chunks/1157.js +9 -0
- package/apps/web/.next/server/chunks/2287.js +1 -0
- package/apps/web/.next/server/chunks/3444.js +1 -0
- package/apps/web/.next/server/chunks/383.js +6 -0
- package/apps/web/.next/server/chunks/4012.js +58 -0
- package/apps/web/.next/server/chunks/6791.js +1 -0
- package/apps/web/.next/server/chunks/7171.js +1 -0
- package/apps/web/.next/server/chunks/8819.js +22 -0
- package/apps/web/.next/server/edge-runtime-webpack.js +2 -0
- package/apps/web/.next/server/edge-runtime-webpack.js.map +1 -0
- package/apps/web/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/apps/web/.next/server/middleware-build-manifest.js +1 -0
- package/apps/web/.next/server/middleware-manifest.json +32 -0
- package/apps/web/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/apps/web/.next/server/next-font-manifest.js +1 -0
- package/apps/web/.next/server/next-font-manifest.json +1 -0
- package/apps/web/.next/server/pages/_app.js +1 -0
- package/apps/web/.next/server/pages/_app.js.nft.json +1 -0
- package/apps/web/.next/server/pages/_document.js +1 -0
- package/apps/web/.next/server/pages/_document.js.nft.json +1 -0
- package/apps/web/.next/server/pages/_error.js +19 -0
- package/apps/web/.next/server/pages/_error.js.nft.json +1 -0
- package/apps/web/.next/server/pages-manifest.json +5 -0
- package/apps/web/.next/server/server-reference-manifest.js +1 -0
- package/apps/web/.next/server/server-reference-manifest.json +1 -0
- package/apps/web/.next/server/src/middleware.js +14 -0
- package/apps/web/.next/server/src/middleware.js.map +1 -0
- package/apps/web/.next/server/webpack-runtime.js +1 -0
- package/apps/web/.next/static/chunks/18-90b700ea37b686a2.js +1 -0
- package/apps/web/.next/static/chunks/87c73c54-24122e7b92478d00.js +1 -0
- package/apps/web/.next/static/chunks/9664-af80478aa73ba424.js +1 -0
- package/apps/web/.next/static/chunks/app/_not-found/page-b9cee17ed89ca24a.js +1 -0
- package/apps/web/.next/static/chunks/app/agents/[slug]/page-18369fc3fe1a9a7b.js +1 -0
- package/apps/web/.next/static/chunks/app/agents/new/page-bf11cf8901c7e2cd.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/access/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/claude-md/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/logs/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/manifest/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/mcps/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/[memId]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/permissions/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/reload/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/restrictions/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/[skillId]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/slack-info/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/restore/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/start/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/stop/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/login/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/logout/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/me/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/users/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/users/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/env-vars/[key]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/env-vars/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/jobs/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/jobs/[id]/runs/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/jobs/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/mcps/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/mcps/[id]/test/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/mcps/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/settings/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/jobs/page-f5aa89a47c50efd8.js +1 -0
- package/apps/web/.next/static/chunks/app/layout-2079f4964aa7314e.js +1 -0
- package/apps/web/.next/static/chunks/app/login/layout-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/login/page-aa259283dc38e8f9.js +1 -0
- package/apps/web/.next/static/chunks/app/page-e83437b608104dff.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/env-vars/page-06479dbdfb78b76b.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/mcps/page-75650686ed6490c7.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/page-e1e62fc41ff6cddd.js +1 -0
- package/apps/web/.next/static/chunks/framework-811407f832a33072.js +1 -0
- package/apps/web/.next/static/chunks/main-3f1cddbdd67b1546.js +1 -0
- package/apps/web/.next/static/chunks/main-app-cebd8a6a5ccbf72d.js +1 -0
- package/apps/web/.next/static/chunks/pages/_app-50fa07b56b2d29ac.js +1 -0
- package/apps/web/.next/static/chunks/pages/_error-fed8688bdd23f211.js +1 -0
- package/apps/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/apps/web/.next/static/chunks/webpack-6c05566dba553c97.js +1 -0
- package/apps/web/.next/static/css/15371687405525e2.css +5 -0
- package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_buildManifest.js +1 -0
- package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_ssgManifest.js +1 -0
- package/apps/web/.next/trace +5 -0
- package/apps/web/.next/types/app/agents/[slug]/page.ts +84 -0
- package/apps/web/.next/types/app/agents/new/page.ts +84 -0
- package/apps/web/.next/types/app/api/agents/[id]/access/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/claude-md/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/logs/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/manifest/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/mcps/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/memories/[memId]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/memories/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/permissions/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/reload/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/restrictions/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/skills/[skillId]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/skills/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/slack-info/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/snapshots/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/start/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/stop/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/login/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/logout/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/me/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/users/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/users/route.ts +347 -0
- package/apps/web/.next/types/app/api/env-vars/[key]/route.ts +347 -0
- package/apps/web/.next/types/app/api/env-vars/route.ts +347 -0
- package/apps/web/.next/types/app/api/jobs/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/jobs/[id]/runs/route.ts +347 -0
- package/apps/web/.next/types/app/api/jobs/route.ts +347 -0
- package/apps/web/.next/types/app/api/mcps/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/mcps/[id]/test/route.ts +347 -0
- package/apps/web/.next/types/app/api/mcps/route.ts +347 -0
- package/apps/web/.next/types/app/api/settings/route.ts +347 -0
- package/apps/web/.next/types/app/jobs/page.ts +84 -0
- package/apps/web/.next/types/app/login/layout.ts +84 -0
- package/apps/web/.next/types/app/login/page.ts +84 -0
- package/apps/web/.next/types/app/page.ts +84 -0
- package/apps/web/.next/types/app/settings/env-vars/page.ts +84 -0
- package/apps/web/.next/types/app/settings/mcps/page.ts +84 -0
- package/apps/web/.next/types/app/settings/page.ts +84 -0
- package/apps/web/.next/types/cache-life.d.ts +141 -0
- package/apps/web/.next/types/package.json +1 -0
- package/apps/web/.next/types/routes.d.ts +114 -0
- package/apps/web/.next/types/validator.ts +448 -0
- package/apps/web/Dockerfile +37 -0
- package/apps/web/next-env.d.ts +6 -0
- package/apps/web/next.config.js +6 -0
- package/apps/web/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/apps/web/package.json +48 -0
- package/apps/web/postcss.config.js +3 -0
- package/apps/web/public/logo.svg +17 -0
- package/apps/web/src/app/agents/[slug]/page.tsx +2235 -0
- package/apps/web/src/app/agents/new/page.tsx +1161 -0
- package/apps/web/src/app/api/agents/[id]/access/route.ts +76 -0
- package/apps/web/src/app/api/agents/[id]/claude-md/route.ts +111 -0
- package/apps/web/src/app/api/agents/[id]/logs/route.ts +84 -0
- package/apps/web/src/app/api/agents/[id]/manifest/route.ts +32 -0
- package/apps/web/src/app/api/agents/[id]/mcps/route.ts +73 -0
- package/apps/web/src/app/api/agents/[id]/memories/[memId]/route.ts +31 -0
- package/apps/web/src/app/api/agents/[id]/memories/route.ts +56 -0
- package/apps/web/src/app/api/agents/[id]/permissions/route.ts +74 -0
- package/apps/web/src/app/api/agents/[id]/reload/route.ts +33 -0
- package/apps/web/src/app/api/agents/[id]/restrictions/route.ts +85 -0
- package/apps/web/src/app/api/agents/[id]/route.ts +81 -0
- package/apps/web/src/app/api/agents/[id]/skills/[skillId]/route.ts +52 -0
- package/apps/web/src/app/api/agents/[id]/skills/route.ts +80 -0
- package/apps/web/src/app/api/agents/[id]/slack-info/route.ts +38 -0
- package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +61 -0
- package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/route.ts +53 -0
- package/apps/web/src/app/api/agents/[id]/snapshots/route.ts +84 -0
- package/apps/web/src/app/api/agents/[id]/start/route.ts +35 -0
- package/apps/web/src/app/api/agents/[id]/stop/route.ts +35 -0
- package/apps/web/src/app/api/agents/route.ts +99 -0
- package/apps/web/src/app/api/auth/login/route.ts +39 -0
- package/apps/web/src/app/api/auth/logout/route.ts +21 -0
- package/apps/web/src/app/api/auth/me/route.ts +24 -0
- package/apps/web/src/app/api/auth/users/[id]/route.ts +48 -0
- package/apps/web/src/app/api/auth/users/route.ts +63 -0
- package/apps/web/src/app/api/env-vars/[key]/route.ts +66 -0
- package/apps/web/src/app/api/env-vars/route.ts +59 -0
- package/apps/web/src/app/api/jobs/[id]/route.ts +51 -0
- package/apps/web/src/app/api/jobs/[id]/runs/route.ts +24 -0
- package/apps/web/src/app/api/jobs/route.ts +42 -0
- package/apps/web/src/app/api/mcps/[id]/route.ts +60 -0
- package/apps/web/src/app/api/mcps/[id]/test/route.ts +195 -0
- package/apps/web/src/app/api/mcps/route.ts +72 -0
- package/apps/web/src/app/api/settings/route.ts +42 -0
- package/apps/web/src/app/globals.css +124 -0
- package/apps/web/src/app/icon.svg +17 -0
- package/apps/web/src/app/jobs/page.tsx +543 -0
- package/apps/web/src/app/layout-shell.tsx +89 -0
- package/apps/web/src/app/layout.tsx +18 -0
- package/apps/web/src/app/login/layout.tsx +9 -0
- package/apps/web/src/app/login/page.tsx +150 -0
- package/apps/web/src/app/page.tsx +573 -0
- package/apps/web/src/app/settings/env-vars/page.tsx +216 -0
- package/apps/web/src/app/settings/mcps/page.tsx +763 -0
- package/apps/web/src/app/settings/page.tsx +528 -0
- package/apps/web/src/app/sidebar.tsx +345 -0
- package/apps/web/src/lib/__tests__/api-guard.test.ts +189 -0
- package/apps/web/src/lib/__tests__/auth.test.ts +262 -0
- package/apps/web/src/lib/__tests__/boss-registry.test.ts +323 -0
- package/apps/web/src/lib/__tests__/compile.test.ts +161 -0
- package/apps/web/src/lib/__tests__/db-agent-hierarchy.test.ts +136 -0
- package/apps/web/src/lib/__tests__/db-env-vars.test.ts +216 -0
- package/apps/web/src/lib/__tests__/db-restrictions.test.ts +117 -0
- package/apps/web/src/lib/__tests__/db.integration.test.ts +271 -0
- package/apps/web/src/lib/__tests__/diff.test.ts +102 -0
- package/apps/web/src/lib/__tests__/mcp-mask.test.ts +274 -0
- package/apps/web/src/lib/__tests__/skill-templates.test.ts +237 -0
- package/apps/web/src/lib/__tests__/slack-manifest.test.ts +105 -0
- package/apps/web/src/lib/api-guard.ts +68 -0
- package/apps/web/src/lib/auth-context.tsx +71 -0
- package/apps/web/src/lib/auth.ts +128 -0
- package/apps/web/src/lib/boss-registry.ts +90 -0
- package/apps/web/src/lib/compile.ts +51 -0
- package/apps/web/src/lib/db.ts +1196 -0
- package/apps/web/src/lib/diff.ts +43 -0
- package/apps/web/src/lib/mcp-mask.ts +91 -0
- package/apps/web/src/lib/portal.tsx +23 -0
- package/apps/web/src/lib/skill-templates.ts +148 -0
- package/apps/web/src/lib/slack-manifest.ts +85 -0
- package/apps/web/src/middleware.ts +68 -0
- package/apps/web/tailwind.config.js +6 -0
- package/apps/web/tsconfig.json +23 -0
- package/apps/web/vitest.config.mts +21 -0
- package/cli/.claude/settings.local.json +6 -0
- package/cli/README.md +281 -0
- package/cli/node_modules/.package-lock.json +427 -0
- package/cli/node_modules/commander/LICENSE +22 -0
- package/cli/node_modules/commander/Readme.md +1157 -0
- package/cli/node_modules/commander/esm.mjs +16 -0
- package/cli/node_modules/commander/index.js +24 -0
- package/cli/node_modules/commander/lib/argument.js +149 -0
- package/cli/node_modules/commander/lib/command.js +2509 -0
- package/cli/node_modules/commander/lib/error.js +39 -0
- package/cli/node_modules/commander/lib/help.js +520 -0
- package/cli/node_modules/commander/lib/option.js +330 -0
- package/cli/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/cli/node_modules/commander/package-support.json +16 -0
- package/cli/node_modules/commander/package.json +84 -0
- package/cli/node_modules/commander/typings/esm.d.mts +3 -0
- package/cli/node_modules/commander/typings/index.d.ts +969 -0
- package/cli/package-lock.json +449 -0
- package/cli/package.json +44 -0
- package/cli/src/commands/init.ts +514 -0
- package/cli/src/commands/manage.ts +115 -0
- package/cli/src/index.ts +63 -0
- package/cli/tsconfig.json +14 -0
- package/docker-compose.yml +122 -0
- package/docs/agents/boss-agents.mdx +108 -0
- package/docs/agents/creating-agents.mdx +132 -0
- package/docs/agents/memory.mdx +113 -0
- package/docs/agents/tools.mdx +103 -0
- package/docs/configuration/env-vars.mdx +166 -0
- package/docs/configuration/mcp-servers.mdx +203 -0
- package/docs/configuration/slack-app.mdx +175 -0
- package/docs/docs.json +79 -0
- package/docs/favicon.svg +17 -0
- package/docs/features/history.mdx +60 -0
- package/docs/features/import-export.mdx +77 -0
- package/docs/features/logs.mdx +131 -0
- package/docs/features/multi-workspace.mdx +90 -0
- package/docs/features/scheduled-jobs.mdx +231 -0
- package/docs/features/users.mdx +92 -0
- package/docs/introduction.mdx +160 -0
- package/docs/logo/dark.svg +17 -0
- package/docs/logo/light.svg +17 -0
- package/docs/logo/wide-dark.svg +12 -0
- package/docs/logo/wide-light.svg +12 -0
- package/docs/quickstart.mdx +270 -0
- package/docs/self-hosting/docker.mdx +151 -0
- package/docs/self-hosting/production.mdx +176 -0
- package/package.json +20 -36
- package/packages/shared/dist/index.d.ts +8 -0
- package/packages/shared/dist/index.d.ts.map +1 -0
- package/packages/shared/dist/index.js +24 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/shared/dist/types.d.ts +584 -0
- package/packages/shared/dist/types.d.ts.map +1 -0
- package/packages/shared/dist/types.js +39 -0
- package/packages/shared/dist/types.js.map +1 -0
- package/packages/shared/package.json +15 -0
- package/packages/shared/src/db/schema.sql +354 -0
- package/packages/shared/src/index.ts +8 -0
- package/packages/shared/src/types.ts +683 -0
- package/packages/shared/tsconfig.json +17 -0
- package/scripts/dev.sh +45 -0
- /package/{dist → cli/dist}/commands/init.d.ts +0 -0
- /package/{dist → cli/dist}/commands/init.js +0 -0
- /package/{dist → cli/dist}/commands/manage.d.ts +0 -0
- /package/{dist → cli/dist}/commands/manage.js +0 -0
- /package/{dist → cli/dist}/index.d.ts +0 -0
- /package/{dist → cli/dist}/index.js +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for skill-templates.ts — SKILL_TEMPLATES.
|
|
3
|
+
*
|
|
4
|
+
* Verifies each template generates the correct skill files with expected
|
|
5
|
+
* content, structure, and agent name/persona interpolation.
|
|
6
|
+
*
|
|
7
|
+
* No database or network required.
|
|
8
|
+
*
|
|
9
|
+
* @module web/lib/__tests__/skill-templates.test
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { SKILL_TEMPLATES } from '@/lib/skill-templates';
|
|
14
|
+
import type { Agent } from '@slackhive/shared';
|
|
15
|
+
|
|
16
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
|
19
|
+
return {
|
|
20
|
+
id: 'agent-1',
|
|
21
|
+
slug: 'test-bot',
|
|
22
|
+
name: 'TestBot',
|
|
23
|
+
persona: undefined,
|
|
24
|
+
description: undefined,
|
|
25
|
+
slackBotToken: 'xoxb-fake',
|
|
26
|
+
slackAppToken: 'xapp-fake',
|
|
27
|
+
slackSigningSecret: 'secret',
|
|
28
|
+
slackBotUserId: undefined,
|
|
29
|
+
model: 'claude-opus-4-5',
|
|
30
|
+
status: 'stopped',
|
|
31
|
+
enabled: true,
|
|
32
|
+
isBoss: false,
|
|
33
|
+
reportsTo: [],
|
|
34
|
+
claudeMd: '',
|
|
35
|
+
createdBy: 'system',
|
|
36
|
+
createdAt: new Date(),
|
|
37
|
+
updatedAt: new Date(),
|
|
38
|
+
...overrides,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Common assertions ────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function expectValidSkills(skills: ReturnType<typeof SKILL_TEMPLATES['blank']>) {
|
|
45
|
+
expect(skills.length).toBeGreaterThan(0);
|
|
46
|
+
for (const skill of skills) {
|
|
47
|
+
expect(skill.category).toBeTruthy();
|
|
48
|
+
expect(skill.filename).toMatch(/\.md$/);
|
|
49
|
+
expect(skill.content.trim()).toBeTruthy();
|
|
50
|
+
expect(typeof skill.sortOrder).toBe('number');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function expectUniqueSortOrders(skills: ReturnType<typeof SKILL_TEMPLATES['blank']>) {
|
|
55
|
+
const orders = skills.map(s => s.sortOrder);
|
|
56
|
+
expect(new Set(orders).size).toBe(orders.length);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── blank template ───────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
describe('SKILL_TEMPLATES.blank', () => {
|
|
62
|
+
it('returns exactly one skill file', () => {
|
|
63
|
+
const skills = SKILL_TEMPLATES.blank(makeAgent());
|
|
64
|
+
expect(skills).toHaveLength(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('interpolates agent name into the skill heading', () => {
|
|
68
|
+
const skills = SKILL_TEMPLATES.blank(makeAgent({ name: 'MyBot' }));
|
|
69
|
+
expect(skills[0].content).toContain('# MyBot');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('uses agent persona when provided', () => {
|
|
73
|
+
const skills = SKILL_TEMPLATES.blank(makeAgent({ persona: 'A grumpy assistant.' }));
|
|
74
|
+
expect(skills[0].content).toContain('A grumpy assistant.');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('generates default persona when persona is undefined', () => {
|
|
78
|
+
const skills = SKILL_TEMPLATES.blank(makeAgent({ name: 'MyBot', persona: undefined }));
|
|
79
|
+
expect(skills[0].content).toContain('You are MyBot');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('includes description when provided', () => {
|
|
83
|
+
const skills = SKILL_TEMPLATES.blank(makeAgent({ description: 'Handles billing queries.' }));
|
|
84
|
+
expect(skills[0].content).toContain('Handles billing queries.');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('omits description section when description is undefined', () => {
|
|
88
|
+
const skills = SKILL_TEMPLATES.blank(makeAgent({ description: undefined }));
|
|
89
|
+
expect(skills[0].content).not.toContain('## What you do');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('produces a valid skill structure', () => {
|
|
93
|
+
expectValidSkills(SKILL_TEMPLATES.blank(makeAgent()));
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ─── data-analyst template ────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
describe('SKILL_TEMPLATES.data-analyst', () => {
|
|
100
|
+
it('returns 3 skill files', () => {
|
|
101
|
+
expect(SKILL_TEMPLATES['data-analyst'](makeAgent())).toHaveLength(3);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('interpolates agent name in identity skill', () => {
|
|
105
|
+
const skills = SKILL_TEMPLATES['data-analyst'](makeAgent({ name: 'DataBot' }));
|
|
106
|
+
const identity = skills.find(s => s.filename === 'identity.md')!;
|
|
107
|
+
expect(identity.content).toContain('# DataBot');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('uses agent persona when provided', () => {
|
|
111
|
+
const skills = SKILL_TEMPLATES['data-analyst'](makeAgent({ persona: 'Expert SQL analyst.' }));
|
|
112
|
+
const identity = skills.find(s => s.filename === 'identity.md')!;
|
|
113
|
+
expect(identity.content).toContain('Expert SQL analyst.');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('includes workflow.md with query execution steps', () => {
|
|
117
|
+
const skills = SKILL_TEMPLATES['data-analyst'](makeAgent());
|
|
118
|
+
const workflow = skills.find(s => s.filename === 'workflow.md')!;
|
|
119
|
+
expect(workflow).toBeDefined();
|
|
120
|
+
expect(workflow.content).toContain('Understand');
|
|
121
|
+
expect(workflow.content).toContain('Execute');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('includes response-format.md', () => {
|
|
125
|
+
const skills = SKILL_TEMPLATES['data-analyst'](makeAgent());
|
|
126
|
+
expect(skills.find(s => s.filename === 'response-format.md')).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('skills are in ascending sortOrder', () => {
|
|
130
|
+
const skills = SKILL_TEMPLATES['data-analyst'](makeAgent());
|
|
131
|
+
const orders = skills.map(s => s.sortOrder);
|
|
132
|
+
expect(orders).toEqual([...orders].sort((a, b) => a - b));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('has unique sort orders', () => {
|
|
136
|
+
expectUniqueSortOrders(SKILL_TEMPLATES['data-analyst'](makeAgent()));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('produces valid skill structures', () => {
|
|
140
|
+
expectValidSkills(SKILL_TEMPLATES['data-analyst'](makeAgent()));
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ─── writer template ──────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
describe('SKILL_TEMPLATES.writer', () => {
|
|
147
|
+
it('returns 2 skill files', () => {
|
|
148
|
+
expect(SKILL_TEMPLATES.writer(makeAgent())).toHaveLength(2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('interpolates agent name in identity skill', () => {
|
|
152
|
+
const skills = SKILL_TEMPLATES.writer(makeAgent({ name: 'WriterBot' }));
|
|
153
|
+
const identity = skills.find(s => s.filename === 'identity.md')!;
|
|
154
|
+
expect(identity.content).toContain('# WriterBot');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('includes style-guide.md with writing guidelines', () => {
|
|
158
|
+
const skills = SKILL_TEMPLATES.writer(makeAgent());
|
|
159
|
+
const guide = skills.find(s => s.filename === 'style-guide.md')!;
|
|
160
|
+
expect(guide).toBeDefined();
|
|
161
|
+
expect(guide.content).toContain('Active voice');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('has unique sort orders', () => {
|
|
165
|
+
expectUniqueSortOrders(SKILL_TEMPLATES.writer(makeAgent()));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('produces valid skill structures', () => {
|
|
169
|
+
expectValidSkills(SKILL_TEMPLATES.writer(makeAgent()));
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ─── developer template ───────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
describe('SKILL_TEMPLATES.developer', () => {
|
|
176
|
+
it('returns 2 skill files', () => {
|
|
177
|
+
expect(SKILL_TEMPLATES.developer(makeAgent())).toHaveLength(2);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('interpolates agent name in identity skill', () => {
|
|
181
|
+
const skills = SKILL_TEMPLATES.developer(makeAgent({ name: 'DevBot' }));
|
|
182
|
+
const identity = skills.find(s => s.filename === 'identity.md')!;
|
|
183
|
+
expect(identity.content).toContain('# DevBot');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('includes code-standards.md with security mention', () => {
|
|
187
|
+
const skills = SKILL_TEMPLATES.developer(makeAgent());
|
|
188
|
+
const standards = skills.find(s => s.filename === 'code-standards.md')!;
|
|
189
|
+
expect(standards).toBeDefined();
|
|
190
|
+
expect(standards.content).toContain('security');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('has unique sort orders', () => {
|
|
194
|
+
expectUniqueSortOrders(SKILL_TEMPLATES.developer(makeAgent()));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('produces valid skill structures', () => {
|
|
198
|
+
expectValidSkills(SKILL_TEMPLATES.developer(makeAgent()));
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ─── All templates ────────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
describe('SKILL_TEMPLATES (all)', () => {
|
|
205
|
+
const templateNames = ['blank', 'data-analyst', 'writer', 'developer'] as const;
|
|
206
|
+
|
|
207
|
+
it('all templates are defined', () => {
|
|
208
|
+
for (const name of templateNames) {
|
|
209
|
+
expect(SKILL_TEMPLATES[name]).toBeTypeOf('function');
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('all templates produce skills in 00-core category', () => {
|
|
214
|
+
const agent = makeAgent();
|
|
215
|
+
for (const name of templateNames) {
|
|
216
|
+
const skills = SKILL_TEMPLATES[name](agent);
|
|
217
|
+
expect(skills.every(s => s.category === '00-core')).toBe(true);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('all templates include an identity.md file', () => {
|
|
222
|
+
const agent = makeAgent();
|
|
223
|
+
for (const name of templateNames) {
|
|
224
|
+
const skills = SKILL_TEMPLATES[name](agent);
|
|
225
|
+
expect(skills.find(s => s.filename === 'identity.md')).toBeDefined();
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('all identity.md files start with the agent name as heading', () => {
|
|
230
|
+
const agent = makeAgent({ name: 'SpecialAgent' });
|
|
231
|
+
for (const name of templateNames) {
|
|
232
|
+
const skills = SKILL_TEMPLATES[name](agent);
|
|
233
|
+
const identity = skills.find(s => s.filename === 'identity.md')!;
|
|
234
|
+
expect(identity.content).toContain('# SpecialAgent');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for slack-manifest.ts — generateSlackManifest.
|
|
3
|
+
*
|
|
4
|
+
* Verifies manifest structure, scope assignment, deduplication, and defaults
|
|
5
|
+
* for both regular and boss agents.
|
|
6
|
+
*
|
|
7
|
+
* No database or network required.
|
|
8
|
+
*
|
|
9
|
+
* @module web/lib/__tests__/slack-manifest.test
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { generateSlackManifest } from '@/lib/slack-manifest';
|
|
14
|
+
import { DEFAULT_SLACK_BOT_SCOPES, BOSS_ADDITIONAL_SCOPES } from '@slackhive/shared';
|
|
15
|
+
|
|
16
|
+
// ─── generateSlackManifest ────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
describe('generateSlackManifest', () => {
|
|
19
|
+
it('sets display name from opts.name', () => {
|
|
20
|
+
const m = generateSlackManifest({ name: 'DataBot' });
|
|
21
|
+
expect(m.display_information.name).toBe('DataBot');
|
|
22
|
+
expect(m.features.bot_user.display_name).toBe('DataBot');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('uses provided description in display_information', () => {
|
|
26
|
+
const m = generateSlackManifest({ name: 'Bot', description: 'My custom description' });
|
|
27
|
+
expect(m.display_information.description).toBe('My custom description');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('generates default description from name when description is omitted', () => {
|
|
31
|
+
const m = generateSlackManifest({ name: 'DataBot' });
|
|
32
|
+
expect(m.display_information.description).toBe('DataBot — Claude Code AI agent');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('includes all DEFAULT_SLACK_BOT_SCOPES for a non-boss agent', () => {
|
|
36
|
+
const m = generateSlackManifest({ name: 'Bot', isBoss: false });
|
|
37
|
+
for (const scope of DEFAULT_SLACK_BOT_SCOPES) {
|
|
38
|
+
expect(m.oauth_config.scopes.bot).toContain(scope);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('includes boss scopes in addition to default scopes for boss agent', () => {
|
|
43
|
+
const m = generateSlackManifest({ name: 'Boss', isBoss: true });
|
|
44
|
+
const allExpected = [...new Set([...DEFAULT_SLACK_BOT_SCOPES, ...BOSS_ADDITIONAL_SCOPES])];
|
|
45
|
+
for (const scope of allExpected) {
|
|
46
|
+
expect(m.oauth_config.scopes.bot).toContain(scope);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('does not include boss scopes for non-boss agent', () => {
|
|
51
|
+
const bossOnlyScopes = BOSS_ADDITIONAL_SCOPES.filter(s => !DEFAULT_SLACK_BOT_SCOPES.includes(s));
|
|
52
|
+
const m = generateSlackManifest({ name: 'Bot', isBoss: false });
|
|
53
|
+
for (const scope of bossOnlyScopes) {
|
|
54
|
+
expect(m.oauth_config.scopes.bot).not.toContain(scope);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('deduplicates scopes — no scope appears twice', () => {
|
|
59
|
+
const m = generateSlackManifest({ name: 'Boss', isBoss: true });
|
|
60
|
+
const scopes = m.oauth_config.scopes.bot;
|
|
61
|
+
const unique = [...new Set(scopes)];
|
|
62
|
+
expect(scopes.length).toBe(unique.length);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('enables socket_mode', () => {
|
|
66
|
+
const m = generateSlackManifest({ name: 'Bot' });
|
|
67
|
+
expect(m.settings.socket_mode_enabled).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('subscribes to all required bot events', () => {
|
|
71
|
+
const m = generateSlackManifest({ name: 'Bot' });
|
|
72
|
+
const events = m.settings.event_subscriptions.bot_events;
|
|
73
|
+
expect(events).toContain('app_mention');
|
|
74
|
+
expect(events).toContain('message.im');
|
|
75
|
+
expect(events).toContain('message.channels');
|
|
76
|
+
expect(events).toContain('message.groups');
|
|
77
|
+
expect(events).toContain('member_joined_channel');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('sets bot always_online to true', () => {
|
|
81
|
+
const m = generateSlackManifest({ name: 'Bot' });
|
|
82
|
+
expect(m.features.bot_user.always_online).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('disables interactivity', () => {
|
|
86
|
+
const m = generateSlackManifest({ name: 'Bot' });
|
|
87
|
+
expect(m.settings.interactivity.is_enabled).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('disables token rotation', () => {
|
|
91
|
+
const m = generateSlackManifest({ name: 'Bot' });
|
|
92
|
+
expect(m.settings.token_rotation_enabled).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('treats isBoss=undefined the same as isBoss=false (no extra scopes)', () => {
|
|
96
|
+
const withFalse = generateSlackManifest({ name: 'Bot', isBoss: false });
|
|
97
|
+
const withUndefined = generateSlackManifest({ name: 'Bot' });
|
|
98
|
+
expect(withFalse.oauth_config.scopes.bot).toEqual(withUndefined.oauth_config.scopes.bot);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('sets background_color to #1a1a1a', () => {
|
|
102
|
+
const m = generateSlackManifest({ name: 'Bot' });
|
|
103
|
+
expect(m.display_information.background_color).toBe('#1a1a1a');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview API route guards — role-based access control for mutations.
|
|
3
|
+
*
|
|
4
|
+
* guardEditor: allows editor, admin, superadmin (blocks viewer)
|
|
5
|
+
* guardAdmin: allows admin, superadmin only (blocks viewer + editor)
|
|
6
|
+
*
|
|
7
|
+
* @module web/lib/api-guard
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { NextResponse } from 'next/server';
|
|
11
|
+
import { getSessionFromRequest, type Role } from './auth';
|
|
12
|
+
import { userCanWriteAgent } from './db';
|
|
13
|
+
|
|
14
|
+
const ROLE_LEVEL: Record<Role, number> = { viewer: 0, editor: 1, admin: 2, superadmin: 3 };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns 403 if the user lacks editor role or above.
|
|
18
|
+
* Allows: editor, admin, superadmin. Blocks: viewer.
|
|
19
|
+
*
|
|
20
|
+
* @param {Request} req - Incoming request.
|
|
21
|
+
* @returns {NextResponse | null} 403 response or null if authorized.
|
|
22
|
+
*/
|
|
23
|
+
export function guardAdmin(req: Request): NextResponse | null {
|
|
24
|
+
const session = getSessionFromRequest(req);
|
|
25
|
+
if (!session) {
|
|
26
|
+
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
|
27
|
+
}
|
|
28
|
+
if ((ROLE_LEVEL[session.role] ?? -1) < ROLE_LEVEL.editor) {
|
|
29
|
+
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns 403 if the user cannot write to the specified agent.
|
|
36
|
+
* Admins/superadmins always pass. Editors pass only if they created the agent
|
|
37
|
+
* or have been explicitly granted write access.
|
|
38
|
+
*
|
|
39
|
+
* @param {Request} req - Incoming request.
|
|
40
|
+
* @param {string} agentId - Agent UUID to check write access for.
|
|
41
|
+
* @returns {Promise<NextResponse | null>} 403 response or null if authorized.
|
|
42
|
+
*/
|
|
43
|
+
export async function guardAgentWrite(req: Request, agentId: string): Promise<NextResponse | null> {
|
|
44
|
+
const session = getSessionFromRequest(req);
|
|
45
|
+
if (!session) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
|
46
|
+
const allowed = await userCanWriteAgent(agentId, session.username, session.role);
|
|
47
|
+
if (!allowed) return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns 403 if the user lacks admin role or above.
|
|
53
|
+
* Allows: admin, superadmin. Blocks: viewer, editor.
|
|
54
|
+
* Use this for user management endpoints only.
|
|
55
|
+
*
|
|
56
|
+
* @param {Request} req - Incoming request.
|
|
57
|
+
* @returns {NextResponse | null} 403 response or null if authorized.
|
|
58
|
+
*/
|
|
59
|
+
export function guardUserAdmin(req: Request): NextResponse | null {
|
|
60
|
+
const session = getSessionFromRequest(req);
|
|
61
|
+
if (!session) {
|
|
62
|
+
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
|
63
|
+
}
|
|
64
|
+
if ((ROLE_LEVEL[session.role] ?? -1) < ROLE_LEVEL.admin) {
|
|
65
|
+
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Client-side auth context — provides role info to components.
|
|
5
|
+
*
|
|
6
|
+
* Roles: superadmin > admin > editor > viewer
|
|
7
|
+
* - canEdit: editor, admin, superadmin (can create/edit agents, jobs, settings)
|
|
8
|
+
* - canManageUsers: admin, superadmin only (can create/delete users)
|
|
9
|
+
*
|
|
10
|
+
* @module web/lib/auth-context
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
14
|
+
|
|
15
|
+
interface AuthState {
|
|
16
|
+
username: string;
|
|
17
|
+
role: 'superadmin' | 'admin' | 'editor' | 'viewer' | null;
|
|
18
|
+
loading: boolean;
|
|
19
|
+
/** Can create/edit/delete agents, jobs, settings, MCPs. */
|
|
20
|
+
canEdit: boolean;
|
|
21
|
+
/** Can create/delete users (admin + superadmin only). */
|
|
22
|
+
canManageUsers: boolean;
|
|
23
|
+
logout: () => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const AuthContext = createContext<AuthState>({
|
|
27
|
+
username: '', role: null, loading: true, canEdit: false, canManageUsers: false, logout: async () => {},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Provides auth state to the component tree.
|
|
32
|
+
*
|
|
33
|
+
* @param {{ children: React.ReactNode }} props
|
|
34
|
+
* @returns {JSX.Element}
|
|
35
|
+
*/
|
|
36
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
37
|
+
const [username, setUsername] = useState('');
|
|
38
|
+
const [role, setRole] = useState<AuthState['role']>(null);
|
|
39
|
+
const [loading, setLoading] = useState(true);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
fetch('/api/auth/me')
|
|
43
|
+
.then(r => { if (!r.ok) throw new Error(); return r.json(); })
|
|
44
|
+
.then(data => { setUsername(data.username); setRole(data.role); })
|
|
45
|
+
.catch(() => {})
|
|
46
|
+
.finally(() => setLoading(false));
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const canEdit = role === 'superadmin' || role === 'admin' || role === 'editor';
|
|
50
|
+
const canManageUsers = role === 'superadmin' || role === 'admin';
|
|
51
|
+
|
|
52
|
+
const logout = async () => {
|
|
53
|
+
await fetch('/api/auth/logout', { method: 'POST' });
|
|
54
|
+
window.location.href = '/login';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<AuthContext.Provider value={{ username, role, loading, canEdit, canManageUsers, logout }}>
|
|
59
|
+
{children}
|
|
60
|
+
</AuthContext.Provider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Hook to access the current auth state.
|
|
66
|
+
*
|
|
67
|
+
* @returns {AuthState} Current auth state.
|
|
68
|
+
*/
|
|
69
|
+
export function useAuth(): AuthState {
|
|
70
|
+
return useContext(AuthContext);
|
|
71
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Authentication utilities — cookie signing, user verification, role checks.
|
|
3
|
+
*
|
|
4
|
+
* Superadmin is checked against env vars (ADMIN_USERNAME/ADMIN_PASSWORD).
|
|
5
|
+
* DB users are checked with bcrypt. Sessions are HMAC-signed cookies.
|
|
6
|
+
*
|
|
7
|
+
* @module web/lib/auth
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as crypto from 'crypto';
|
|
11
|
+
import bcrypt from 'bcryptjs';
|
|
12
|
+
import { getUserByUsername } from './db';
|
|
13
|
+
|
|
14
|
+
if (process.env.NODE_ENV === 'production' && !process.env.CI && (!process.env.AUTH_SECRET || !process.env.ADMIN_PASSWORD)) {
|
|
15
|
+
throw new Error('AUTH_SECRET and ADMIN_PASSWORD must be set in production. See .env.example.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const AUTH_SECRET = process.env.AUTH_SECRET || 'change-this-secret-in-production';
|
|
19
|
+
const ADMIN_USERNAME = process.env.ADMIN_USERNAME || 'admin';
|
|
20
|
+
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'changeme';
|
|
21
|
+
const COOKIE_NAME = 'auth_session';
|
|
22
|
+
|
|
23
|
+
export type Role = 'superadmin' | 'admin' | 'editor' | 'viewer';
|
|
24
|
+
|
|
25
|
+
export interface SessionPayload {
|
|
26
|
+
username: string;
|
|
27
|
+
role: Role;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Signs a session payload into an HMAC cookie value.
|
|
32
|
+
*
|
|
33
|
+
* @param {SessionPayload} payload - The session data to sign.
|
|
34
|
+
* @returns {string} Signed cookie value (base64payload.base64sig).
|
|
35
|
+
*/
|
|
36
|
+
export function signSession(payload: SessionPayload): string {
|
|
37
|
+
const data = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
38
|
+
const sig = crypto.createHmac('sha256', AUTH_SECRET).update(data).digest('base64url');
|
|
39
|
+
return `${data}.${sig}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Verifies an HMAC-signed session cookie and returns the payload.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} cookie - The signed cookie value.
|
|
46
|
+
* @returns {SessionPayload | null} Decoded payload or null if invalid.
|
|
47
|
+
*/
|
|
48
|
+
export function verifySession(cookie: string): SessionPayload | null {
|
|
49
|
+
const parts = cookie.split('.');
|
|
50
|
+
if (parts.length !== 2) return null;
|
|
51
|
+
const [data, sig] = parts;
|
|
52
|
+
const expected = crypto.createHmac('sha256', AUTH_SECRET).update(data).digest('base64url');
|
|
53
|
+
if (sig !== expected) return null;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(Buffer.from(data, 'base64url').toString()) as SessionPayload;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Authenticates a user by username and password.
|
|
63
|
+
* Checks superadmin env vars first, then DB users.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} username - The username to authenticate.
|
|
66
|
+
* @param {string} password - The plaintext password.
|
|
67
|
+
* @returns {Promise<SessionPayload | null>} Session payload or null if auth fails.
|
|
68
|
+
*/
|
|
69
|
+
export async function authenticateUser(username: string, password: string): Promise<SessionPayload | null> {
|
|
70
|
+
// Check superadmin
|
|
71
|
+
if (username === ADMIN_USERNAME && password === ADMIN_PASSWORD) {
|
|
72
|
+
return { username, role: 'superadmin' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check DB users
|
|
76
|
+
const user = await getUserByUsername(username);
|
|
77
|
+
if (!user) return null;
|
|
78
|
+
|
|
79
|
+
const valid = await bcrypt.compare(password, user.passwordHash);
|
|
80
|
+
if (!valid) return null;
|
|
81
|
+
|
|
82
|
+
return { username: user.username, role: user.role as Role };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Hashes a plaintext password with bcrypt.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} password - Plaintext password.
|
|
89
|
+
* @returns {Promise<string>} Bcrypt hash.
|
|
90
|
+
*/
|
|
91
|
+
export async function hashPassword(password: string): Promise<string> {
|
|
92
|
+
return bcrypt.hash(password, 10);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extracts session from a Request's cookies.
|
|
97
|
+
*
|
|
98
|
+
* @param {Request} req - Incoming request.
|
|
99
|
+
* @returns {SessionPayload | null} Session payload or null.
|
|
100
|
+
*/
|
|
101
|
+
export function getSessionFromRequest(req: Request): SessionPayload | null {
|
|
102
|
+
const cookieHeader = req.headers.get('cookie') || '';
|
|
103
|
+
const match = cookieHeader.match(new RegExp(`${COOKIE_NAME}=([^;]+)`));
|
|
104
|
+
if (!match) return null;
|
|
105
|
+
return verifySession(decodeURIComponent(match[1]));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Requires at least the given role. Returns the session or throws.
|
|
110
|
+
*
|
|
111
|
+
* @param {Request} req - Incoming request.
|
|
112
|
+
* @param {'viewer' | 'admin'} minRole - Minimum required role.
|
|
113
|
+
* @returns {SessionPayload} Verified session.
|
|
114
|
+
* @throws {Error} If not authenticated or insufficient role.
|
|
115
|
+
*/
|
|
116
|
+
export function requireRole(req: Request, minRole: 'viewer' | 'editor' | 'admin'): SessionPayload {
|
|
117
|
+
const session = getSessionFromRequest(req);
|
|
118
|
+
if (!session) throw new Error('Not authenticated');
|
|
119
|
+
|
|
120
|
+
const roleLevel: Record<Role, number> = { viewer: 0, editor: 1, admin: 2, superadmin: 3 };
|
|
121
|
+
if ((roleLevel[session.role] ?? -1) < (roleLevel[minRole] ?? 0)) {
|
|
122
|
+
throw new Error('Insufficient permissions');
|
|
123
|
+
}
|
|
124
|
+
return session;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The cookie name used for auth sessions. */
|
|
128
|
+
export { COOKIE_NAME };
|