availsync 0.1.0
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/.adal/skills/stripe-best-practices/SKILL.md +42 -0
- package/.adal/skills/stripe-best-practices/references/billing.md +36 -0
- package/.adal/skills/stripe-best-practices/references/connect.md +48 -0
- package/.adal/skills/stripe-best-practices/references/payments.md +79 -0
- package/.adal/skills/stripe-best-practices/references/security.md +109 -0
- package/.adal/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.adal/skills/stripe-projects/SKILL.md +139 -0
- package/.adal/skills/upgrade-stripe/SKILL.md +185 -0
- package/.agents/skills/stripe-best-practices/SKILL.md +42 -0
- package/.agents/skills/stripe-best-practices/references/billing.md +36 -0
- package/.agents/skills/stripe-best-practices/references/connect.md +48 -0
- package/.agents/skills/stripe-best-practices/references/payments.md +79 -0
- package/.agents/skills/stripe-best-practices/references/security.md +109 -0
- package/.agents/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.agents/skills/stripe-projects/SKILL.md +139 -0
- package/.agents/skills/upgrade-stripe/SKILL.md +185 -0
- package/.augment/skills/stripe-best-practices/SKILL.md +42 -0
- package/.augment/skills/stripe-best-practices/references/billing.md +36 -0
- package/.augment/skills/stripe-best-practices/references/connect.md +48 -0
- package/.augment/skills/stripe-best-practices/references/payments.md +79 -0
- package/.augment/skills/stripe-best-practices/references/security.md +109 -0
- package/.augment/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.augment/skills/stripe-projects/SKILL.md +139 -0
- package/.augment/skills/upgrade-stripe/SKILL.md +185 -0
- package/.bob/skills/stripe-best-practices/SKILL.md +42 -0
- package/.bob/skills/stripe-best-practices/references/billing.md +36 -0
- package/.bob/skills/stripe-best-practices/references/connect.md +48 -0
- package/.bob/skills/stripe-best-practices/references/payments.md +79 -0
- package/.bob/skills/stripe-best-practices/references/security.md +109 -0
- package/.bob/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.bob/skills/stripe-projects/SKILL.md +139 -0
- package/.bob/skills/upgrade-stripe/SKILL.md +185 -0
- package/.claude/settings.local.json +7 -0
- package/.claude/skills/stripe-best-practices/SKILL.md +42 -0
- package/.claude/skills/stripe-best-practices/references/billing.md +36 -0
- package/.claude/skills/stripe-best-practices/references/connect.md +48 -0
- package/.claude/skills/stripe-best-practices/references/payments.md +79 -0
- package/.claude/skills/stripe-best-practices/references/security.md +109 -0
- package/.claude/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.claude/skills/stripe-projects/SKILL.md +139 -0
- package/.claude/skills/upgrade-stripe/SKILL.md +185 -0
- package/.codebuddy/skills/stripe-best-practices/SKILL.md +42 -0
- package/.codebuddy/skills/stripe-best-practices/references/billing.md +36 -0
- package/.codebuddy/skills/stripe-best-practices/references/connect.md +48 -0
- package/.codebuddy/skills/stripe-best-practices/references/payments.md +79 -0
- package/.codebuddy/skills/stripe-best-practices/references/security.md +109 -0
- package/.codebuddy/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.codebuddy/skills/stripe-projects/SKILL.md +139 -0
- package/.codebuddy/skills/upgrade-stripe/SKILL.md +185 -0
- package/.commandcode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.commandcode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.commandcode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.commandcode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.commandcode/skills/stripe-best-practices/references/security.md +109 -0
- package/.commandcode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.commandcode/skills/stripe-projects/SKILL.md +139 -0
- package/.commandcode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.continue/skills/stripe-best-practices/SKILL.md +42 -0
- package/.continue/skills/stripe-best-practices/references/billing.md +36 -0
- package/.continue/skills/stripe-best-practices/references/connect.md +48 -0
- package/.continue/skills/stripe-best-practices/references/payments.md +79 -0
- package/.continue/skills/stripe-best-practices/references/security.md +109 -0
- package/.continue/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.continue/skills/stripe-projects/SKILL.md +139 -0
- package/.continue/skills/upgrade-stripe/SKILL.md +185 -0
- package/.cortex/skills/stripe-best-practices/SKILL.md +42 -0
- package/.cortex/skills/stripe-best-practices/references/billing.md +36 -0
- package/.cortex/skills/stripe-best-practices/references/connect.md +48 -0
- package/.cortex/skills/stripe-best-practices/references/payments.md +79 -0
- package/.cortex/skills/stripe-best-practices/references/security.md +109 -0
- package/.cortex/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.cortex/skills/stripe-projects/SKILL.md +139 -0
- package/.cortex/skills/upgrade-stripe/SKILL.md +185 -0
- package/.crush/skills/stripe-best-practices/SKILL.md +42 -0
- package/.crush/skills/stripe-best-practices/references/billing.md +36 -0
- package/.crush/skills/stripe-best-practices/references/connect.md +48 -0
- package/.crush/skills/stripe-best-practices/references/payments.md +79 -0
- package/.crush/skills/stripe-best-practices/references/security.md +109 -0
- package/.crush/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.crush/skills/stripe-projects/SKILL.md +139 -0
- package/.crush/skills/upgrade-stripe/SKILL.md +185 -0
- package/.env.example +20 -0
- package/.factory/skills/stripe-best-practices/SKILL.md +42 -0
- package/.factory/skills/stripe-best-practices/references/billing.md +36 -0
- package/.factory/skills/stripe-best-practices/references/connect.md +48 -0
- package/.factory/skills/stripe-best-practices/references/payments.md +79 -0
- package/.factory/skills/stripe-best-practices/references/security.md +109 -0
- package/.factory/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.factory/skills/stripe-projects/SKILL.md +139 -0
- package/.factory/skills/upgrade-stripe/SKILL.md +185 -0
- package/.goose/skills/stripe-best-practices/SKILL.md +42 -0
- package/.goose/skills/stripe-best-practices/references/billing.md +36 -0
- package/.goose/skills/stripe-best-practices/references/connect.md +48 -0
- package/.goose/skills/stripe-best-practices/references/payments.md +79 -0
- package/.goose/skills/stripe-best-practices/references/security.md +109 -0
- package/.goose/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.goose/skills/stripe-projects/SKILL.md +139 -0
- package/.goose/skills/upgrade-stripe/SKILL.md +185 -0
- package/.iflow/skills/stripe-best-practices/SKILL.md +42 -0
- package/.iflow/skills/stripe-best-practices/references/billing.md +36 -0
- package/.iflow/skills/stripe-best-practices/references/connect.md +48 -0
- package/.iflow/skills/stripe-best-practices/references/payments.md +79 -0
- package/.iflow/skills/stripe-best-practices/references/security.md +109 -0
- package/.iflow/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.iflow/skills/stripe-projects/SKILL.md +139 -0
- package/.iflow/skills/upgrade-stripe/SKILL.md +185 -0
- package/.junie/skills/stripe-best-practices/SKILL.md +42 -0
- package/.junie/skills/stripe-best-practices/references/billing.md +36 -0
- package/.junie/skills/stripe-best-practices/references/connect.md +48 -0
- package/.junie/skills/stripe-best-practices/references/payments.md +79 -0
- package/.junie/skills/stripe-best-practices/references/security.md +109 -0
- package/.junie/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.junie/skills/stripe-projects/SKILL.md +139 -0
- package/.junie/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kilocode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kilocode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kilocode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kilocode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kilocode/skills/stripe-best-practices/references/security.md +109 -0
- package/.kilocode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kilocode/skills/stripe-projects/SKILL.md +139 -0
- package/.kilocode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kiro/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kiro/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kiro/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kiro/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kiro/skills/stripe-best-practices/references/security.md +109 -0
- package/.kiro/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kiro/skills/stripe-projects/SKILL.md +139 -0
- package/.kiro/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kode/skills/stripe-best-practices/references/security.md +109 -0
- package/.kode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kode/skills/stripe-projects/SKILL.md +139 -0
- package/.kode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.mcpjam/skills/stripe-best-practices/SKILL.md +42 -0
- package/.mcpjam/skills/stripe-best-practices/references/billing.md +36 -0
- package/.mcpjam/skills/stripe-best-practices/references/connect.md +48 -0
- package/.mcpjam/skills/stripe-best-practices/references/payments.md +79 -0
- package/.mcpjam/skills/stripe-best-practices/references/security.md +109 -0
- package/.mcpjam/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.mcpjam/skills/stripe-projects/SKILL.md +139 -0
- package/.mcpjam/skills/upgrade-stripe/SKILL.md +185 -0
- package/.mux/skills/stripe-best-practices/SKILL.md +42 -0
- package/.mux/skills/stripe-best-practices/references/billing.md +36 -0
- package/.mux/skills/stripe-best-practices/references/connect.md +48 -0
- package/.mux/skills/stripe-best-practices/references/payments.md +79 -0
- package/.mux/skills/stripe-best-practices/references/security.md +109 -0
- package/.mux/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.mux/skills/stripe-projects/SKILL.md +139 -0
- package/.mux/skills/upgrade-stripe/SKILL.md +185 -0
- package/.neovate/skills/stripe-best-practices/SKILL.md +42 -0
- package/.neovate/skills/stripe-best-practices/references/billing.md +36 -0
- package/.neovate/skills/stripe-best-practices/references/connect.md +48 -0
- package/.neovate/skills/stripe-best-practices/references/payments.md +79 -0
- package/.neovate/skills/stripe-best-practices/references/security.md +109 -0
- package/.neovate/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.neovate/skills/stripe-projects/SKILL.md +139 -0
- package/.neovate/skills/upgrade-stripe/SKILL.md +185 -0
- package/.nixpacksignore +14 -0
- package/.openhands/skills/stripe-best-practices/SKILL.md +42 -0
- package/.openhands/skills/stripe-best-practices/references/billing.md +36 -0
- package/.openhands/skills/stripe-best-practices/references/connect.md +48 -0
- package/.openhands/skills/stripe-best-practices/references/payments.md +79 -0
- package/.openhands/skills/stripe-best-practices/references/security.md +109 -0
- package/.openhands/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.openhands/skills/stripe-projects/SKILL.md +139 -0
- package/.openhands/skills/upgrade-stripe/SKILL.md +185 -0
- package/.pi/skills/stripe-best-practices/SKILL.md +42 -0
- package/.pi/skills/stripe-best-practices/references/billing.md +36 -0
- package/.pi/skills/stripe-best-practices/references/connect.md +48 -0
- package/.pi/skills/stripe-best-practices/references/payments.md +79 -0
- package/.pi/skills/stripe-best-practices/references/security.md +109 -0
- package/.pi/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.pi/skills/stripe-projects/SKILL.md +139 -0
- package/.pi/skills/upgrade-stripe/SKILL.md +185 -0
- package/.pochi/skills/stripe-best-practices/SKILL.md +42 -0
- package/.pochi/skills/stripe-best-practices/references/billing.md +36 -0
- package/.pochi/skills/stripe-best-practices/references/connect.md +48 -0
- package/.pochi/skills/stripe-best-practices/references/payments.md +79 -0
- package/.pochi/skills/stripe-best-practices/references/security.md +109 -0
- package/.pochi/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.pochi/skills/stripe-projects/SKILL.md +139 -0
- package/.pochi/skills/upgrade-stripe/SKILL.md +185 -0
- package/.qoder/skills/stripe-best-practices/SKILL.md +42 -0
- package/.qoder/skills/stripe-best-practices/references/billing.md +36 -0
- package/.qoder/skills/stripe-best-practices/references/connect.md +48 -0
- package/.qoder/skills/stripe-best-practices/references/payments.md +79 -0
- package/.qoder/skills/stripe-best-practices/references/security.md +109 -0
- package/.qoder/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.qoder/skills/stripe-projects/SKILL.md +139 -0
- package/.qoder/skills/upgrade-stripe/SKILL.md +185 -0
- package/.qwen/skills/stripe-best-practices/SKILL.md +42 -0
- package/.qwen/skills/stripe-best-practices/references/billing.md +36 -0
- package/.qwen/skills/stripe-best-practices/references/connect.md +48 -0
- package/.qwen/skills/stripe-best-practices/references/payments.md +79 -0
- package/.qwen/skills/stripe-best-practices/references/security.md +109 -0
- package/.qwen/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.qwen/skills/stripe-projects/SKILL.md +139 -0
- package/.qwen/skills/upgrade-stripe/SKILL.md +185 -0
- package/.roo/skills/stripe-best-practices/SKILL.md +42 -0
- package/.roo/skills/stripe-best-practices/references/billing.md +36 -0
- package/.roo/skills/stripe-best-practices/references/connect.md +48 -0
- package/.roo/skills/stripe-best-practices/references/payments.md +79 -0
- package/.roo/skills/stripe-best-practices/references/security.md +109 -0
- package/.roo/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.roo/skills/stripe-projects/SKILL.md +139 -0
- package/.roo/skills/upgrade-stripe/SKILL.md +185 -0
- package/.trae/skills/stripe-best-practices/SKILL.md +42 -0
- package/.trae/skills/stripe-best-practices/references/billing.md +36 -0
- package/.trae/skills/stripe-best-practices/references/connect.md +48 -0
- package/.trae/skills/stripe-best-practices/references/payments.md +79 -0
- package/.trae/skills/stripe-best-practices/references/security.md +109 -0
- package/.trae/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.trae/skills/stripe-projects/SKILL.md +139 -0
- package/.trae/skills/upgrade-stripe/SKILL.md +185 -0
- package/.vibe/skills/stripe-best-practices/SKILL.md +42 -0
- package/.vibe/skills/stripe-best-practices/references/billing.md +36 -0
- package/.vibe/skills/stripe-best-practices/references/connect.md +48 -0
- package/.vibe/skills/stripe-best-practices/references/payments.md +79 -0
- package/.vibe/skills/stripe-best-practices/references/security.md +109 -0
- package/.vibe/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.vibe/skills/stripe-projects/SKILL.md +139 -0
- package/.vibe/skills/upgrade-stripe/SKILL.md +185 -0
- package/.windsurf/skills/stripe-best-practices/SKILL.md +42 -0
- package/.windsurf/skills/stripe-best-practices/references/billing.md +36 -0
- package/.windsurf/skills/stripe-best-practices/references/connect.md +48 -0
- package/.windsurf/skills/stripe-best-practices/references/payments.md +79 -0
- package/.windsurf/skills/stripe-best-practices/references/security.md +109 -0
- package/.windsurf/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.windsurf/skills/stripe-projects/SKILL.md +139 -0
- package/.windsurf/skills/upgrade-stripe/SKILL.md +185 -0
- package/.zencoder/skills/stripe-best-practices/SKILL.md +42 -0
- package/.zencoder/skills/stripe-best-practices/references/billing.md +36 -0
- package/.zencoder/skills/stripe-best-practices/references/connect.md +48 -0
- package/.zencoder/skills/stripe-best-practices/references/payments.md +79 -0
- package/.zencoder/skills/stripe-best-practices/references/security.md +109 -0
- package/.zencoder/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.zencoder/skills/stripe-projects/SKILL.md +139 -0
- package/.zencoder/skills/upgrade-stripe/SKILL.md +185 -0
- package/AUDIT.md +95 -0
- package/BLOCKERS.md +0 -0
- package/COOLIFY.md +51 -0
- package/MCP_SETUP.md +23 -0
- package/PRODUCTION_CHECKLIST.md +246 -0
- package/README.md +47 -0
- package/ROADMAP.md +91 -0
- package/docs/superpowers/plans/2026-05-11-availsync-frontend-sales-flow.md +2445 -0
- package/frontend/.env.example +2 -0
- package/frontend/app/admin/layout.tsx +13 -0
- package/frontend/app/admin/page.tsx +747 -0
- package/frontend/app/app/activity/page.tsx +257 -0
- package/frontend/app/app/agents/[agentId]/page.tsx +21 -0
- package/frontend/app/app/agents/page.tsx +1155 -0
- package/frontend/app/app/audit/page.tsx +225 -0
- package/frontend/app/app/availability/page.tsx +840 -0
- package/frontend/app/app/holds/page.tsx +262 -0
- package/frontend/app/app/layout.tsx +19 -0
- package/frontend/app/app/onboarding/page.tsx +10 -0
- package/frontend/app/app/onboarding/verify/page.tsx +309 -0
- package/frontend/app/app/page.tsx +508 -0
- package/frontend/app/app/settings/page.tsx +399 -0
- package/frontend/app/app/work/page.tsx +426 -0
- package/frontend/app/changelog/page.tsx +93 -0
- package/frontend/app/checkout/page.tsx +25 -0
- package/frontend/app/docs/api/page.tsx +157 -0
- package/frontend/app/docs/page.tsx +296 -0
- package/frontend/app/docs/pilot/page.tsx +127 -0
- package/frontend/app/docs/quickstart/page.tsx +318 -0
- package/frontend/app/docs/reliability/page.tsx +78 -0
- package/frontend/app/docs/sdk/node/page.tsx +166 -0
- package/frontend/app/globals.css +57 -0
- package/frontend/app/icon.png +0 -0
- package/frontend/app/layout.tsx +87 -0
- package/frontend/app/login/page.tsx +14 -0
- package/frontend/app/page.tsx +47 -0
- package/frontend/app/pricing/page.tsx +66 -0
- package/frontend/app/privacy/page.tsx +52 -0
- package/frontend/app/robots.ts +26 -0
- package/frontend/app/security/page.tsx +74 -0
- package/frontend/app/signup/page.tsx +14 -0
- package/frontend/app/sitemap.ts +14 -0
- package/frontend/app/terms/page.tsx +51 -0
- package/frontend/components/brand/AvailsyncLogo.tsx +56 -0
- package/frontend/components/checkout/CheckoutClient.tsx +100 -0
- package/frontend/components/dashboard/AgentForm.tsx +59 -0
- package/frontend/components/dashboard/AppShell.tsx +291 -0
- package/frontend/components/dashboard/AvailabilityChecker.tsx +117 -0
- package/frontend/components/dashboard/AvailabilityWindowForm.tsx +40 -0
- package/frontend/components/dashboard/HoldForm.tsx +133 -0
- package/frontend/components/dashboard/MetricCard.tsx +10 -0
- package/frontend/components/login/LoginForm.tsx +95 -0
- package/frontend/components/marketing/AgentCoordinationStory.tsx +1530 -0
- package/frontend/components/marketing/Faq.tsx +41 -0
- package/frontend/components/marketing/Hero.tsx +73 -0
- package/frontend/components/marketing/HowItWorks.tsx +28 -0
- package/frontend/components/marketing/ObserveModeTeaser.tsx +41 -0
- package/frontend/components/marketing/PricingTeaser.tsx +23 -0
- package/frontend/components/marketing/ProblemSolution.tsx +36 -0
- package/frontend/components/marketing/SiteFooter.tsx +59 -0
- package/frontend/components/marketing/SiteHeader.tsx +45 -0
- package/frontend/components/marketing/UseCases.tsx +27 -0
- package/frontend/components/onboarding/OnboardingClient.tsx +278 -0
- package/frontend/components/pricing/PricingCards.tsx +65 -0
- package/frontend/components/privacy/CookieConsent.tsx +230 -0
- package/frontend/components/privacy/CookieSettingsButton.tsx +15 -0
- package/frontend/components/seo/JsonLd.tsx +10 -0
- package/frontend/components/signup/SignupForm.tsx +55 -0
- package/frontend/components/ui/Badge.tsx +23 -0
- package/frontend/components/ui/Button.tsx +37 -0
- package/frontend/components/ui/Card.tsx +11 -0
- package/frontend/components/ui/ConfirmDialog.tsx +77 -0
- package/frontend/components/ui/EmptyState.tsx +24 -0
- package/frontend/components/ui/Input.tsx +14 -0
- package/frontend/components/ui/KeyDisplay.tsx +49 -0
- package/frontend/components/ui/Select.tsx +14 -0
- package/frontend/components/ui/Skeleton.tsx +24 -0
- package/frontend/components/ui/Tabs.tsx +19 -0
- package/frontend/components/ui/Textarea.tsx +14 -0
- package/frontend/components/ui/Toast.tsx +78 -0
- package/frontend/components/waitlist/WaitlistDialog.tsx +128 -0
- package/frontend/lib/api.ts +1282 -0
- package/frontend/lib/billing.ts +6 -0
- package/frontend/lib/cookieConsent.ts +113 -0
- package/frontend/lib/format.ts +16 -0
- package/frontend/lib/plans.ts +62 -0
- package/frontend/lib/schemas.ts +108 -0
- package/frontend/lib/seo.ts +376 -0
- package/frontend/lib/setupGuides.ts +630 -0
- package/frontend/lib/storage.ts +30 -0
- package/frontend/next-env.d.ts +6 -0
- package/frontend/next.config.mjs +13 -0
- package/frontend/package-lock.json +14409 -0
- package/frontend/package.json +41 -0
- package/frontend/playwright.config.ts +20 -0
- package/frontend/postcss.config.mjs +8 -0
- package/frontend/public/.gitkeep +0 -0
- package/frontend/public/brand/availsync-logo-board.png +0 -0
- package/frontend/public/brand/availsync-logo-dark.png +0 -0
- package/frontend/public/brand/availsync-mark-dark.png +0 -0
- package/frontend/public/brand/availsync-wordmark-dark.png +0 -0
- package/frontend/public/marketing/hero-agent-coordination.png +0 -0
- package/frontend/tailwind.config.ts +53 -0
- package/frontend/tests/smoke.spec.ts +89 -0
- package/frontend/tsconfig.json +23 -0
- package/jest.config.js +7 -0
- package/nixpacks.toml +11 -0
- package/package.json +53 -0
- package/packages/mcp/LICENSE +21 -0
- package/packages/mcp/README.md +60 -0
- package/packages/mcp/jest.config.cjs +8 -0
- package/packages/mcp/package.json +54 -0
- package/packages/mcp/src/helpers.ts +38 -0
- package/packages/mcp/src/index.test.ts +60 -0
- package/packages/mcp/src/index.ts +387 -0
- package/packages/mcp/tsconfig.json +20 -0
- package/packages/mcp/tsconfig.test.json +12 -0
- package/packages/node/LICENSE +21 -0
- package/packages/node/README.md +120 -0
- package/packages/node/jest.config.cjs +8 -0
- package/packages/node/package.json +46 -0
- package/packages/node/src/index.test.ts +360 -0
- package/packages/node/src/index.ts +402 -0
- package/packages/node/tsconfig.json +20 -0
- package/packages/node/tsconfig.test.json +12 -0
- package/plan.md +923 -0
- package/skills/stripe-best-practices/SKILL.md +42 -0
- package/skills/stripe-best-practices/references/billing.md +36 -0
- package/skills/stripe-best-practices/references/connect.md +48 -0
- package/skills/stripe-best-practices/references/payments.md +79 -0
- package/skills/stripe-best-practices/references/security.md +109 -0
- package/skills/stripe-best-practices/references/treasury.md +16 -0
- package/skills/stripe-projects/SKILL.md +139 -0
- package/skills/upgrade-stripe/SKILL.md +185 -0
- package/skills-lock.json +20 -0
- package/src/core/availability.ts +178 -0
- package/src/core/conflict.ts +209 -0
- package/src/core/work.ts +490 -0
- package/src/db/client.ts +17 -0
- package/src/db/migrations/001_init.sql +88 -0
- package/src/db/migrations/002_stripe.sql +2 -0
- package/src/db/migrations/003_workspace_auth.sql +19 -0
- package/src/db/migrations/004_agent_mcp_status.sql +2 -0
- package/src/db/migrations/005_hold_event_actor.sql +4 -0
- package/src/db/migrations/006_agent_activity.sql +35 -0
- package/src/db/migrations/007_work_coordination.sql +60 -0
- package/src/db/migrations/008_work_claim_leases.sql +20 -0
- package/src/db/migrations/009_billing_subscription_state.sql +23 -0
- package/src/db/migrations/010_agent_api_key_prefix.sql +10 -0
- package/src/db/migrations/011_org_verified_and_work_event_retention.sql +11 -0
- package/src/db/migrations/012_agent_enforcement_mode.sql +12 -0
- package/src/db/migrations/013_support_tickets.sql +21 -0
- package/src/db/migrations/014_paid_plan_waitlist.sql +23 -0
- package/src/db/migrations/015_agent_last_seen.sql +2 -0
- package/src/db/migrations.ts +164 -0
- package/src/db/run-migrations.ts +13 -0
- package/src/index.ts +183 -0
- package/src/lib/activity.ts +137 -0
- package/src/lib/apiKeys.ts +32 -0
- package/src/lib/appInfo.ts +26 -0
- package/src/lib/billingConfig.ts +3 -0
- package/src/lib/env.ts +75 -0
- package/src/lib/logger.ts +8 -0
- package/src/lib/plans.ts +204 -0
- package/src/mcp/server.js +5 -0
- package/src/mcp/server.ts +350 -0
- package/src/middleware/auth.ts +342 -0
- package/src/middleware/requestId.ts +16 -0
- package/src/routes/account.ts +168 -0
- package/src/routes/activity.ts +126 -0
- package/src/routes/admin.ts +514 -0
- package/src/routes/audit.ts +68 -0
- package/src/routes/auth.ts +203 -0
- package/src/routes/availability.ts +325 -0
- package/src/routes/billing.ts +406 -0
- package/src/routes/conflicts.ts +131 -0
- package/src/routes/holds.ts +437 -0
- package/src/routes/mcp.ts +57 -0
- package/src/routes/metrics.ts +39 -0
- package/src/routes/onboarding.ts +273 -0
- package/src/routes/orgs.ts +981 -0
- package/src/routes/preferences.ts +132 -0
- package/src/routes/session.ts +16 -0
- package/src/routes/support.ts +77 -0
- package/src/routes/value.ts +186 -0
- package/src/routes/waitlist.ts +63 -0
- package/src/routes/work.ts +1578 -0
- package/src/server.ts +36 -0
- package/src/types/index.ts +109 -0
- package/tests/integration/activity.route.test.ts +103 -0
- package/tests/integration/admin.route.test.ts +143 -0
- package/tests/integration/agent-keys.route.test.ts +237 -0
- package/tests/integration/availability.route.test.ts +125 -0
- package/tests/integration/billing.route.test.ts +393 -0
- package/tests/integration/conflicts.route.test.ts +131 -0
- package/tests/integration/flows.test.ts +154 -0
- package/tests/integration/helpers.ts +134 -0
- package/tests/integration/holds.route.test.ts +185 -0
- package/tests/integration/metrics.route.test.ts +100 -0
- package/tests/integration/onboarding.verify.route.test.ts +163 -0
- package/tests/integration/preferences.route.test.ts +53 -0
- package/tests/integration/session.route.test.ts +97 -0
- package/tests/integration/system.route.test.ts +92 -0
- package/tests/integration/value.route.test.ts +235 -0
- package/tests/integration/work.route.test.ts +745 -0
- package/tests/setup.ts +4 -0
- package/tests/smoke.sh +62 -0
- package/tests/unit/auth.test.ts +114 -0
- package/tests/unit/availability.test.ts +149 -0
- package/tests/unit/conflict.test.ts +118 -0
- package/tests/unit/env.test.ts +69 -0
- package/tests/unit/migrations.test.ts +135 -0
- package/tests/unit/request-id.test.ts +37 -0
- package/tmp-mobile-agents.png +0 -0
- package/tmp-next-mobile.err.log +10 -0
- package/tmp-next-mobile.log +5 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import pool from '../db/client';
|
|
4
|
+
import { migrationSummary } from '../db/migrations';
|
|
5
|
+
import { appVersion, gitCommit } from '../lib/appInfo';
|
|
6
|
+
import { envReadiness, stripeReadiness } from '../lib/env';
|
|
7
|
+
import { requireAdminSession } from '../middleware/auth';
|
|
8
|
+
import { log } from '../lib/logger';
|
|
9
|
+
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
|
|
12
|
+
const OrgListQuerySchema = z.object({
|
|
13
|
+
plan: z.enum(['free', 'individual', 'team']).optional(),
|
|
14
|
+
q: z.string().max(120).optional(),
|
|
15
|
+
active: z.enum(['today', '7d', '30d']).optional(),
|
|
16
|
+
limit: z.coerce.number().int().min(1).max(200).optional().default(50),
|
|
17
|
+
offset: z.coerce.number().int().min(0).optional().default(0),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const SupportListQuerySchema = z.object({
|
|
21
|
+
status: z.enum(['open', 'in_review', 'closed']).optional(),
|
|
22
|
+
plan: z.enum(['individual', 'team']).optional(),
|
|
23
|
+
q: z.string().max(120).optional(),
|
|
24
|
+
limit: z.coerce.number().int().min(1).max(100).optional().default(50),
|
|
25
|
+
offset: z.coerce.number().int().min(0).optional().default(0),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const SupportUpdateSchema = z.object({
|
|
29
|
+
status: z.enum(['open', 'in_review', 'closed']),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const WaitlistListQuerySchema = z.object({
|
|
33
|
+
status: z.enum(['new', 'contacted', 'closed']).optional(),
|
|
34
|
+
plan: z.enum(['individual', 'team']).optional(),
|
|
35
|
+
q: z.string().max(120).optional(),
|
|
36
|
+
limit: z.coerce.number().int().min(1).max(100).optional().default(50),
|
|
37
|
+
offset: z.coerce.number().int().min(0).optional().default(0),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const WaitlistUpdateSchema = z.object({
|
|
41
|
+
status: z.enum(['new', 'contacted', 'closed']),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
router.get('/admin/overview', requireAdminSession, async (_req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const result = await pool.query(
|
|
47
|
+
`SELECT
|
|
48
|
+
(SELECT COUNT(*)::int FROM orgs) AS total_workspaces,
|
|
49
|
+
(SELECT COUNT(DISTINCT org_id)::int FROM agent_activity_events WHERE created_at >= NOW() - INTERVAL '24 hours') AS active_workspaces,
|
|
50
|
+
(SELECT COUNT(*)::int FROM orgs WHERE plan = 'free') AS free_workspaces,
|
|
51
|
+
(SELECT COUNT(*)::int FROM orgs WHERE plan = 'individual') AS individual_workspaces,
|
|
52
|
+
(SELECT COUNT(*)::int FROM orgs WHERE plan = 'team') AS team_workspaces,
|
|
53
|
+
(SELECT COUNT(*)::int FROM orgs WHERE plan <> 'free') AS paid_workspaces,
|
|
54
|
+
(SELECT COALESCE(SUM(CASE WHEN plan = 'individual' THEN 10 WHEN plan = 'team' THEN 25 ELSE 0 END), 0)::int FROM orgs WHERE subscription_status IN ('active', 'trialing')) AS estimated_mrr,
|
|
55
|
+
(SELECT COUNT(*)::int FROM orgs WHERE subscription_status = 'canceled' OR cancel_at_period_end = true) AS canceled_workspaces,
|
|
56
|
+
(SELECT COUNT(*)::int FROM agents) AS total_agents,
|
|
57
|
+
(SELECT COUNT(*)::int FROM agents WHERE GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz)) > NOW() - INTERVAL '15 minutes') AS connected_agents,
|
|
58
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE created_at >= date_trunc('day', NOW())) AS api_calls_today,
|
|
59
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE created_at >= date_trunc('month', NOW())) AS api_calls_month,
|
|
60
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE status = 'error' AND created_at >= date_trunc('day', NOW())) AS errors_today,
|
|
61
|
+
(SELECT COUNT(DISTINCT org_id)::int FROM agent_activity_events WHERE created_at >= NOW() - INTERVAL '7 days' AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')) AS active_workspaces_7d,
|
|
62
|
+
(SELECT COUNT(*)::int FROM agents WHERE last_seen_at IS NULL AND mcp_last_seen_at IS NULL AND NOT EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification'))) AS never_connected_agents,
|
|
63
|
+
(SELECT COUNT(*)::int FROM agents WHERE GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz), COALESCE((SELECT MAX(created_at) FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')), 'epoch'::timestamptz)) < NOW() - INTERVAL '3 days' AND (last_seen_at IS NOT NULL OR mcp_last_seen_at IS NOT NULL OR EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')))) AS quiet_agents,
|
|
64
|
+
((SELECT COUNT(*)::int FROM work_claim_events WHERE event_type = 'blocked' AND created_at >= NOW() - INTERVAL '7 days' AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')) + (SELECT COUNT(*)::int FROM hold_events WHERE event_type IN ('conflict_won', 'conflict_lost', 'superseded') AND created_at >= NOW() - INTERVAL '7 days')) AS conflicts_prevented_7d,
|
|
65
|
+
(SELECT COUNT(*)::int FROM work_claims WHERE status = 'active' AND expires_at >= NOW()) AS active_work_claims,
|
|
66
|
+
(SELECT COUNT(*)::int FROM work_claim_events WHERE event_type = 'blocked') AS blocked_agent_runs,
|
|
67
|
+
(SELECT COUNT(*)::int FROM paid_plan_waitlist) AS waitlist_leads,
|
|
68
|
+
(SELECT COUNT(*)::int FROM paid_plan_waitlist WHERE status = 'new') AS waitlist_new,
|
|
69
|
+
(SELECT MAX(created_at) FROM orgs) AS latest_signup_at`,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return res.json(result.rows[0]);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
log.error('[admin] overview error', { error: (err as Error).message });
|
|
75
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
router.get('/admin/system', requireAdminSession, async (_req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const [migrations, latest] = await Promise.all([
|
|
82
|
+
migrationSummary(),
|
|
83
|
+
pool.query(
|
|
84
|
+
`SELECT
|
|
85
|
+
(SELECT MAX(created_at) FROM agent_activity_events) AS latest_activity_at,
|
|
86
|
+
(SELECT MAX(created_at) FROM hold_events) AS latest_hold_event_at,
|
|
87
|
+
(SELECT MAX(created_at) FROM work_claim_events) AS latest_work_event_at`,
|
|
88
|
+
),
|
|
89
|
+
]);
|
|
90
|
+
await pool.query('SELECT 1');
|
|
91
|
+
|
|
92
|
+
const env = envReadiness();
|
|
93
|
+
const stripe = stripeReadiness();
|
|
94
|
+
const status = migrations.failed > 0 || !env.ok ? 'degraded' : 'ok';
|
|
95
|
+
|
|
96
|
+
return res.json({
|
|
97
|
+
status,
|
|
98
|
+
app: {
|
|
99
|
+
version: appVersion(),
|
|
100
|
+
git_commit: gitCommit(),
|
|
101
|
+
node_version: process.version,
|
|
102
|
+
uptime_seconds: Math.floor(process.uptime()),
|
|
103
|
+
},
|
|
104
|
+
database: {
|
|
105
|
+
connected: true,
|
|
106
|
+
},
|
|
107
|
+
migrations,
|
|
108
|
+
env,
|
|
109
|
+
stripe,
|
|
110
|
+
latest_events: latest.rows[0],
|
|
111
|
+
timestamp: new Date().toISOString(),
|
|
112
|
+
});
|
|
113
|
+
} catch (err) {
|
|
114
|
+
log.error('[admin] system error', { error: (err as Error).message });
|
|
115
|
+
return res.status(500).json({
|
|
116
|
+
status: 'error',
|
|
117
|
+
error: 'system_diagnostics_failed',
|
|
118
|
+
message: 'Unable to load system diagnostics',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
router.get('/admin/support/tickets', requireAdminSession, async (req, res) => {
|
|
124
|
+
const parsed = SupportListQuerySchema.safeParse(req.query);
|
|
125
|
+
if (!parsed.success) {
|
|
126
|
+
return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const result = await pool.query(
|
|
131
|
+
`SELECT
|
|
132
|
+
support_tickets.id,
|
|
133
|
+
support_tickets.org_id,
|
|
134
|
+
orgs.name AS org_name,
|
|
135
|
+
support_tickets.user_id,
|
|
136
|
+
support_tickets.email,
|
|
137
|
+
support_tickets.subject,
|
|
138
|
+
support_tickets.category,
|
|
139
|
+
support_tickets.message,
|
|
140
|
+
support_tickets.status,
|
|
141
|
+
support_tickets.priority,
|
|
142
|
+
support_tickets.plan,
|
|
143
|
+
support_tickets.context,
|
|
144
|
+
support_tickets.created_at,
|
|
145
|
+
support_tickets.updated_at,
|
|
146
|
+
COUNT(*) OVER()::int AS total_count
|
|
147
|
+
FROM support_tickets
|
|
148
|
+
JOIN orgs ON orgs.id = support_tickets.org_id
|
|
149
|
+
WHERE ($1::text IS NULL OR support_tickets.status = $1)
|
|
150
|
+
AND ($2::text IS NULL OR support_tickets.plan = $2)
|
|
151
|
+
AND (
|
|
152
|
+
$3::text IS NULL
|
|
153
|
+
OR support_tickets.email ILIKE '%' || $3 || '%'
|
|
154
|
+
OR support_tickets.subject ILIKE '%' || $3 || '%'
|
|
155
|
+
OR orgs.name ILIKE '%' || $3 || '%'
|
|
156
|
+
)
|
|
157
|
+
ORDER BY
|
|
158
|
+
CASE support_tickets.status WHEN 'open' THEN 0 WHEN 'in_review' THEN 1 ELSE 2 END,
|
|
159
|
+
support_tickets.created_at DESC
|
|
160
|
+
LIMIT $4 OFFSET $5`,
|
|
161
|
+
[
|
|
162
|
+
parsed.data.status ?? null,
|
|
163
|
+
parsed.data.plan ?? null,
|
|
164
|
+
parsed.data.q ?? null,
|
|
165
|
+
parsed.data.limit,
|
|
166
|
+
parsed.data.offset,
|
|
167
|
+
],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return res.json({
|
|
171
|
+
tickets: result.rows.map(({ total_count: _total, ...row }) => row),
|
|
172
|
+
limit: parsed.data.limit,
|
|
173
|
+
offset: parsed.data.offset,
|
|
174
|
+
count: result.rows.length,
|
|
175
|
+
total: result.rows[0]?.total_count ?? 0,
|
|
176
|
+
});
|
|
177
|
+
} catch (err) {
|
|
178
|
+
log.error('[admin] support list error', { error: (err as Error).message });
|
|
179
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
router.put('/admin/support/tickets/:ticket_id', requireAdminSession, async (req, res) => {
|
|
184
|
+
const ticketId = z.string().uuid().safeParse(req.params.ticket_id);
|
|
185
|
+
const parsed = SupportUpdateSchema.safeParse(req.body);
|
|
186
|
+
if (!ticketId.success || !parsed.success) {
|
|
187
|
+
return res.status(400).json({ error: 'validation_error', message: 'Invalid ticket update' });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const result = await pool.query(
|
|
192
|
+
`UPDATE support_tickets
|
|
193
|
+
SET status = $2,
|
|
194
|
+
updated_at = NOW()
|
|
195
|
+
WHERE id = $1
|
|
196
|
+
RETURNING id, org_id, user_id, email, subject, category, message, status, priority, plan, context, created_at, updated_at`,
|
|
197
|
+
[ticketId.data, parsed.data.status],
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (result.rows.length === 0) {
|
|
201
|
+
return res.status(404).json({ error: 'ticket_not_found', message: 'Support ticket not found' });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return res.json({ ticket: result.rows[0] });
|
|
205
|
+
} catch (err) {
|
|
206
|
+
log.error('[admin] support update error', { error: (err as Error).message, ticket_id: ticketId.data });
|
|
207
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
router.get('/admin/waitlist', requireAdminSession, async (req, res) => {
|
|
212
|
+
const parsed = WaitlistListQuerySchema.safeParse(req.query);
|
|
213
|
+
if (!parsed.success) {
|
|
214
|
+
return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const result = await pool.query(
|
|
219
|
+
`SELECT
|
|
220
|
+
paid_plan_waitlist.id,
|
|
221
|
+
paid_plan_waitlist.org_id,
|
|
222
|
+
orgs.name AS org_name,
|
|
223
|
+
paid_plan_waitlist.user_id,
|
|
224
|
+
paid_plan_waitlist.email,
|
|
225
|
+
paid_plan_waitlist.plan,
|
|
226
|
+
paid_plan_waitlist.source,
|
|
227
|
+
paid_plan_waitlist.message,
|
|
228
|
+
paid_plan_waitlist.status,
|
|
229
|
+
paid_plan_waitlist.context,
|
|
230
|
+
paid_plan_waitlist.created_at,
|
|
231
|
+
paid_plan_waitlist.updated_at,
|
|
232
|
+
COUNT(*) OVER()::int AS total_count
|
|
233
|
+
FROM paid_plan_waitlist
|
|
234
|
+
LEFT JOIN orgs ON orgs.id = paid_plan_waitlist.org_id
|
|
235
|
+
WHERE ($1::text IS NULL OR paid_plan_waitlist.status = $1)
|
|
236
|
+
AND ($2::text IS NULL OR paid_plan_waitlist.plan = $2)
|
|
237
|
+
AND (
|
|
238
|
+
$3::text IS NULL
|
|
239
|
+
OR paid_plan_waitlist.email ILIKE '%' || $3 || '%'
|
|
240
|
+
OR paid_plan_waitlist.message ILIKE '%' || $3 || '%'
|
|
241
|
+
OR orgs.name ILIKE '%' || $3 || '%'
|
|
242
|
+
)
|
|
243
|
+
ORDER BY
|
|
244
|
+
CASE paid_plan_waitlist.status WHEN 'new' THEN 0 WHEN 'contacted' THEN 1 ELSE 2 END,
|
|
245
|
+
paid_plan_waitlist.created_at DESC
|
|
246
|
+
LIMIT $4 OFFSET $5`,
|
|
247
|
+
[
|
|
248
|
+
parsed.data.status ?? null,
|
|
249
|
+
parsed.data.plan ?? null,
|
|
250
|
+
parsed.data.q ?? null,
|
|
251
|
+
parsed.data.limit,
|
|
252
|
+
parsed.data.offset,
|
|
253
|
+
],
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return res.json({
|
|
257
|
+
leads: result.rows.map(({ total_count: _total, ...row }) => row),
|
|
258
|
+
limit: parsed.data.limit,
|
|
259
|
+
offset: parsed.data.offset,
|
|
260
|
+
count: result.rows.length,
|
|
261
|
+
total: result.rows[0]?.total_count ?? 0,
|
|
262
|
+
});
|
|
263
|
+
} catch (err) {
|
|
264
|
+
log.error('[admin] waitlist list error', { error: (err as Error).message });
|
|
265
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
router.put('/admin/waitlist/:lead_id', requireAdminSession, async (req, res) => {
|
|
270
|
+
const leadId = z.string().uuid().safeParse(req.params.lead_id);
|
|
271
|
+
const parsed = WaitlistUpdateSchema.safeParse(req.body);
|
|
272
|
+
if (!leadId.success || !parsed.success) {
|
|
273
|
+
return res.status(400).json({ error: 'validation_error', message: 'Invalid waitlist update' });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
const result = await pool.query(
|
|
278
|
+
`UPDATE paid_plan_waitlist
|
|
279
|
+
SET status = $2,
|
|
280
|
+
updated_at = NOW()
|
|
281
|
+
WHERE id = $1
|
|
282
|
+
RETURNING id, org_id, user_id, email, plan, source, message, status, context, created_at, updated_at`,
|
|
283
|
+
[leadId.data, parsed.data.status],
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (result.rows.length === 0) {
|
|
287
|
+
return res.status(404).json({ error: 'lead_not_found', message: 'Waitlist lead not found' });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return res.json({ lead: result.rows[0] });
|
|
291
|
+
} catch (err) {
|
|
292
|
+
log.error('[admin] waitlist update error', { error: (err as Error).message, lead_id: leadId.data });
|
|
293
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
router.get('/admin/orgs', requireAdminSession, async (req, res) => {
|
|
298
|
+
const parsed = OrgListQuerySchema.safeParse(req.query);
|
|
299
|
+
if (!parsed.success) {
|
|
300
|
+
return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const activeIntervals: Record<string, string> = {
|
|
304
|
+
today: '24 hours',
|
|
305
|
+
'7d': '7 days',
|
|
306
|
+
'30d': '30 days',
|
|
307
|
+
};
|
|
308
|
+
const activeInterval = parsed.data.active ? activeIntervals[parsed.data.active] : null;
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const result = await pool.query(
|
|
312
|
+
`WITH org_stats AS (
|
|
313
|
+
SELECT
|
|
314
|
+
orgs.id,
|
|
315
|
+
orgs.name,
|
|
316
|
+
orgs.plan,
|
|
317
|
+
orgs.agent_limit,
|
|
318
|
+
orgs.subscription_status,
|
|
319
|
+
orgs.current_period_end,
|
|
320
|
+
orgs.cancel_at_period_end,
|
|
321
|
+
orgs.stripe_customer_id IS NOT NULL AS has_stripe_customer,
|
|
322
|
+
orgs.stripe_subscription_id IS NOT NULL AS has_stripe_subscription,
|
|
323
|
+
orgs.created_at,
|
|
324
|
+
owner.email AS owner_email,
|
|
325
|
+
(SELECT COUNT(*)::int FROM agents WHERE agents.org_id = orgs.id) AS agent_count,
|
|
326
|
+
(SELECT COUNT(*)::int FROM agents WHERE agents.org_id = orgs.id AND GREATEST(COALESCE(agents.last_seen_at, 'epoch'::timestamptz), COALESCE(agents.mcp_last_seen_at, 'epoch'::timestamptz)) > NOW() - INTERVAL '15 minutes') AS connected_agent_count,
|
|
327
|
+
(SELECT COUNT(*)::int FROM work_claims WHERE work_claims.org_id = orgs.id AND work_claims.status = 'active' AND work_claims.expires_at >= NOW()) AS active_work_claims,
|
|
328
|
+
(SELECT COUNT(*)::int FROM work_resources WHERE work_resources.org_id = orgs.id) AS protected_resources,
|
|
329
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id AND agent_activity_events.created_at > NOW() - INTERVAL '24 hours') AS api_calls_24h,
|
|
330
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id AND agent_activity_events.created_at >= date_trunc('month', NOW())) AS api_calls_month,
|
|
331
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id AND agent_activity_events.status = 'error' AND agent_activity_events.created_at > NOW() - INTERVAL '24 hours') AS errors_24h,
|
|
332
|
+
(SELECT MAX(created_at) FROM agent_activity_events WHERE agent_activity_events.org_id = orgs.id) AS last_activity_at
|
|
333
|
+
FROM orgs
|
|
334
|
+
LEFT JOIN LATERAL (
|
|
335
|
+
SELECT email
|
|
336
|
+
FROM workspace_users
|
|
337
|
+
WHERE workspace_users.org_id = orgs.id
|
|
338
|
+
ORDER BY CASE WHEN role = 'owner' THEN 0 ELSE 1 END, created_at ASC
|
|
339
|
+
LIMIT 1
|
|
340
|
+
) owner ON true
|
|
341
|
+
)
|
|
342
|
+
SELECT *, COUNT(*) OVER()::int AS total_count
|
|
343
|
+
FROM org_stats
|
|
344
|
+
WHERE ($1::text IS NULL OR plan = $1)
|
|
345
|
+
AND ($2::text IS NULL OR name ILIKE '%' || $2 || '%' OR owner_email ILIKE '%' || $2 || '%')
|
|
346
|
+
AND ($3::text IS NULL OR last_activity_at > NOW() - ($3::interval))
|
|
347
|
+
ORDER BY created_at DESC
|
|
348
|
+
LIMIT $4 OFFSET $5`,
|
|
349
|
+
[
|
|
350
|
+
parsed.data.plan ?? null,
|
|
351
|
+
parsed.data.q ?? null,
|
|
352
|
+
activeInterval,
|
|
353
|
+
parsed.data.limit,
|
|
354
|
+
parsed.data.offset,
|
|
355
|
+
],
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
return res.json({
|
|
359
|
+
orgs: result.rows.map(({ total_count: _total, ...row }) => row),
|
|
360
|
+
limit: parsed.data.limit,
|
|
361
|
+
offset: parsed.data.offset,
|
|
362
|
+
count: result.rows.length,
|
|
363
|
+
total: result.rows[0]?.total_count ?? 0,
|
|
364
|
+
});
|
|
365
|
+
} catch (err) {
|
|
366
|
+
log.error('[admin] org list error', { error: (err as Error).message });
|
|
367
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
router.get('/admin/orgs/:org_id', requireAdminSession, async (req, res) => {
|
|
372
|
+
const orgId = z.string().uuid().safeParse(req.params.org_id);
|
|
373
|
+
if (!orgId.success) {
|
|
374
|
+
return res.status(400).json({ error: 'validation_error', message: 'Invalid org id' });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const org = await pool.query(
|
|
379
|
+
`SELECT
|
|
380
|
+
id,
|
|
381
|
+
name,
|
|
382
|
+
plan,
|
|
383
|
+
agent_limit,
|
|
384
|
+
subscription_status,
|
|
385
|
+
current_period_end,
|
|
386
|
+
cancel_at_period_end,
|
|
387
|
+
stripe_customer_id IS NOT NULL AS has_stripe_customer,
|
|
388
|
+
stripe_subscription_id IS NOT NULL AS has_stripe_subscription,
|
|
389
|
+
created_at
|
|
390
|
+
FROM orgs
|
|
391
|
+
WHERE id = $1`,
|
|
392
|
+
[orgId.data],
|
|
393
|
+
);
|
|
394
|
+
if (org.rows.length === 0) {
|
|
395
|
+
return res.status(404).json({ error: 'org_not_found', message: 'Organization not found' });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const [users, agents, usage, activity, holdEvents, workEvents] = await Promise.all([
|
|
399
|
+
pool.query(
|
|
400
|
+
`SELECT id, email, role, created_at
|
|
401
|
+
FROM workspace_users
|
|
402
|
+
WHERE org_id = $1
|
|
403
|
+
ORDER BY created_at ASC`,
|
|
404
|
+
[orgId.data],
|
|
405
|
+
),
|
|
406
|
+
pool.query(
|
|
407
|
+
`SELECT
|
|
408
|
+
id,
|
|
409
|
+
name,
|
|
410
|
+
agent_type,
|
|
411
|
+
priority,
|
|
412
|
+
last_seen_at,
|
|
413
|
+
mcp_last_seen_at,
|
|
414
|
+
mcp_client_name,
|
|
415
|
+
created_at,
|
|
416
|
+
(SELECT MAX(created_at) FROM agent_activity_events WHERE agent_id = agents.id) AS last_activity_at,
|
|
417
|
+
(SELECT MAX(created_at) FROM agent_activity_events WHERE agent_id = agents.id AND status = 'error') AS last_error_at
|
|
418
|
+
FROM agents
|
|
419
|
+
WHERE org_id = $1
|
|
420
|
+
ORDER BY created_at DESC`,
|
|
421
|
+
[orgId.data],
|
|
422
|
+
),
|
|
423
|
+
pool.query(
|
|
424
|
+
`SELECT
|
|
425
|
+
(SELECT COUNT(*)::int FROM agents WHERE org_id = $1) AS agents,
|
|
426
|
+
(SELECT COUNT(*)::int FROM agents WHERE org_id = $1 AND GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz)) > NOW() - INTERVAL '15 minutes') AS connected_agents,
|
|
427
|
+
(SELECT COUNT(*)::int FROM work_resources WHERE org_id = $1) AS protected_resources,
|
|
428
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE org_id = $1 AND created_at > NOW() - INTERVAL '24 hours') AS api_calls_24h,
|
|
429
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE org_id = $1 AND created_at >= date_trunc('month', NOW())) AS api_calls_month,
|
|
430
|
+
(SELECT COUNT(*)::int FROM agent_activity_events WHERE org_id = $1 AND status = 'error' AND created_at > NOW() - INTERVAL '24 hours') AS errors_24h,
|
|
431
|
+
(SELECT COALESCE(SUM(total_tokens), 0)::int FROM agent_activity_events WHERE org_id = $1 AND created_at > NOW() - INTERVAL '24 hours') AS tokens_24h,
|
|
432
|
+
(SELECT COUNT(*)::int FROM agents WHERE org_id = $1 AND last_seen_at IS NULL AND mcp_last_seen_at IS NULL AND NOT EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification'))) AS never_connected_agents,
|
|
433
|
+
(SELECT COUNT(*)::int FROM agents WHERE org_id = $1 AND GREATEST(COALESCE(last_seen_at, 'epoch'::timestamptz), COALESCE(mcp_last_seen_at, 'epoch'::timestamptz), COALESCE((SELECT MAX(created_at) FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')), 'epoch'::timestamptz)) < NOW() - INTERVAL '3 days' AND (last_seen_at IS NOT NULL OR mcp_last_seen_at IS NOT NULL OR EXISTS (SELECT 1 FROM agent_activity_events WHERE agent_activity_events.agent_id = agents.id AND activity_type NOT LIKE 'setup_test%' AND activity_type NOT IN ('verification_run', 'connection_test') AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')))) AS quiet_agents,
|
|
434
|
+
((SELECT COUNT(*)::int FROM work_claim_events WHERE org_id = $1 AND event_type = 'blocked' AND created_at >= NOW() - INTERVAL '7 days' AND COALESCE(metadata->>'source', '') NOT IN ('setup_test', 'onboarding_verification')) + (SELECT COUNT(*)::int FROM hold_events WHERE org_id = $1 AND event_type IN ('conflict_won', 'conflict_lost', 'superseded') AND created_at >= NOW() - INTERVAL '7 days')) AS conflicts_prevented_7d,
|
|
435
|
+
(SELECT COUNT(*)::int FROM work_claims WHERE org_id = $1 AND status = 'active' AND expires_at >= NOW()) AS active_work_claims,
|
|
436
|
+
(SELECT COUNT(*)::int FROM work_claim_events WHERE org_id = $1 AND event_type = 'blocked') AS blocked_agent_runs,
|
|
437
|
+
(SELECT MAX(created_at) FROM agent_activity_events WHERE org_id = $1) AS last_activity_at`,
|
|
438
|
+
[orgId.data],
|
|
439
|
+
),
|
|
440
|
+
pool.query(
|
|
441
|
+
`SELECT
|
|
442
|
+
agent_activity_events.id,
|
|
443
|
+
agent_activity_events.agent_id,
|
|
444
|
+
agents.name AS agent_name,
|
|
445
|
+
agent_activity_events.activity_type,
|
|
446
|
+
agent_activity_events.endpoint,
|
|
447
|
+
agent_activity_events.method,
|
|
448
|
+
agent_activity_events.status_code,
|
|
449
|
+
agent_activity_events.status,
|
|
450
|
+
agent_activity_events.error_code,
|
|
451
|
+
agent_activity_events.error_message,
|
|
452
|
+
agent_activity_events.created_at
|
|
453
|
+
FROM agent_activity_events
|
|
454
|
+
LEFT JOIN agents ON agents.id = agent_activity_events.agent_id
|
|
455
|
+
WHERE agent_activity_events.org_id = $1
|
|
456
|
+
ORDER BY agent_activity_events.created_at DESC
|
|
457
|
+
LIMIT 20`,
|
|
458
|
+
[orgId.data],
|
|
459
|
+
),
|
|
460
|
+
pool.query(
|
|
461
|
+
`SELECT
|
|
462
|
+
hold_events.id,
|
|
463
|
+
hold_events.event_type,
|
|
464
|
+
hold_events.agent_id,
|
|
465
|
+
agents.name AS agent_name,
|
|
466
|
+
hold_events.rule_applied,
|
|
467
|
+
hold_events.actor_type,
|
|
468
|
+
hold_events.actor_label,
|
|
469
|
+
hold_events.created_at
|
|
470
|
+
FROM hold_events
|
|
471
|
+
LEFT JOIN agents ON agents.id = hold_events.agent_id
|
|
472
|
+
WHERE hold_events.org_id = $1
|
|
473
|
+
ORDER BY hold_events.created_at DESC
|
|
474
|
+
LIMIT 20`,
|
|
475
|
+
[orgId.data],
|
|
476
|
+
),
|
|
477
|
+
pool.query(
|
|
478
|
+
`SELECT
|
|
479
|
+
work_claim_events.id,
|
|
480
|
+
work_claim_events.event_type,
|
|
481
|
+
work_claim_events.agent_id,
|
|
482
|
+
agents.name AS agent_name,
|
|
483
|
+
work_resources.resource_type,
|
|
484
|
+
work_resources.resource_key,
|
|
485
|
+
work_claim_events.rule_applied,
|
|
486
|
+
work_claim_events.actor_type,
|
|
487
|
+
work_claim_events.actor_label,
|
|
488
|
+
work_claim_events.created_at
|
|
489
|
+
FROM work_claim_events
|
|
490
|
+
LEFT JOIN agents ON agents.id = work_claim_events.agent_id
|
|
491
|
+
LEFT JOIN work_resources ON work_resources.id = work_claim_events.resource_id
|
|
492
|
+
WHERE work_claim_events.org_id = $1
|
|
493
|
+
ORDER BY work_claim_events.created_at DESC
|
|
494
|
+
LIMIT 20`,
|
|
495
|
+
[orgId.data],
|
|
496
|
+
),
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
return res.json({
|
|
500
|
+
org: org.rows[0],
|
|
501
|
+
users: users.rows,
|
|
502
|
+
agents: agents.rows,
|
|
503
|
+
usage: usage.rows[0],
|
|
504
|
+
recent_activity: activity.rows,
|
|
505
|
+
recent_hold_events: holdEvents.rows,
|
|
506
|
+
recent_work_events: workEvents.rows,
|
|
507
|
+
});
|
|
508
|
+
} catch (err) {
|
|
509
|
+
log.error('[admin] org detail error', { error: (err as Error).message });
|
|
510
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
export default router;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import pool from '../db/client';
|
|
4
|
+
import { requireWorkspaceSession, type WorkspaceAuthenticatedRequest } from '../middleware/auth';
|
|
5
|
+
import { log } from '../lib/logger';
|
|
6
|
+
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
const AuditQuerySchema = z.object({
|
|
10
|
+
limit: z.coerce.number().int().min(1).max(200).optional().default(50),
|
|
11
|
+
offset: z.coerce.number().int().min(0).optional().default(0),
|
|
12
|
+
agent_id: z.string().uuid().optional(),
|
|
13
|
+
event_type: z.enum(['created', 'superseded', 'released', 'conflict_won', 'conflict_lost']).optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
router.get('/audit', requireWorkspaceSession, async (req, res) => {
|
|
17
|
+
const parsed = AuditQuerySchema.safeParse(req.query);
|
|
18
|
+
if (!parsed.success) {
|
|
19
|
+
return res.status(400).json({ error: 'validation_error', details: parsed.error.flatten() });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const authReq = req as WorkspaceAuthenticatedRequest;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const result = await pool.query(
|
|
26
|
+
`SELECT
|
|
27
|
+
he.id,
|
|
28
|
+
he.hold_id,
|
|
29
|
+
he.event_type,
|
|
30
|
+
he.agent_id,
|
|
31
|
+
a.name AS agent_name,
|
|
32
|
+
he.org_id,
|
|
33
|
+
he.conflicting_hold_id,
|
|
34
|
+
he.rule_applied,
|
|
35
|
+
he.metadata,
|
|
36
|
+
he.actor_type,
|
|
37
|
+
he.actor_id,
|
|
38
|
+
he.actor_label,
|
|
39
|
+
he.created_at
|
|
40
|
+
FROM hold_events he
|
|
41
|
+
LEFT JOIN agents a ON a.id = he.agent_id
|
|
42
|
+
WHERE he.org_id = $1
|
|
43
|
+
AND ($4::uuid IS NULL OR he.agent_id = $4)
|
|
44
|
+
AND ($5::text IS NULL OR he.event_type = $5)
|
|
45
|
+
ORDER BY he.created_at DESC
|
|
46
|
+
LIMIT $2 OFFSET $3`,
|
|
47
|
+
[
|
|
48
|
+
authReq.org.id,
|
|
49
|
+
parsed.data.limit,
|
|
50
|
+
parsed.data.offset,
|
|
51
|
+
parsed.data.agent_id ?? null,
|
|
52
|
+
parsed.data.event_type ?? null,
|
|
53
|
+
],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return res.json({
|
|
57
|
+
events: result.rows,
|
|
58
|
+
limit: parsed.data.limit,
|
|
59
|
+
offset: parsed.data.offset,
|
|
60
|
+
count: result.rows.length,
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
log.error('[audit] unexpected error', { error: (err as Error).message });
|
|
64
|
+
return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export default router;
|