vibecarbon 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/LICENSE +663 -0
- package/README.md +188 -0
- package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
- package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
- package/carbon/.claude/settings.json +29 -0
- package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
- package/carbon/.dockerignore +57 -0
- package/carbon/.env.example +219 -0
- package/carbon/.github/copilot-instructions.md +3 -0
- package/carbon/.github/workflows/deploy.yml +346 -0
- package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
- package/carbon/.windsurfrules +74 -0
- package/carbon/AGENTS.md +422 -0
- package/carbon/CLAUDE.md +3 -0
- package/carbon/DEVELOPMENT.md +187 -0
- package/carbon/Dockerfile +98 -0
- package/carbon/LICENSE +21 -0
- package/carbon/PRODUCTION.md +364 -0
- package/carbon/README.md +193 -0
- package/carbon/TESTING.md +138 -0
- package/carbon/backup/Dockerfile +75 -0
- package/carbon/backup/backup.sh +95 -0
- package/carbon/backup/compose-backup.sh +140 -0
- package/carbon/biome.json +83 -0
- package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
- package/carbon/cloud-init/k3s/master-init.sh +215 -0
- package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
- package/carbon/cloud-init/k3s/worker-init.sh +147 -0
- package/carbon/components.json +24 -0
- package/carbon/content/blog/authentication-guide.mdx +34 -0
- package/carbon/content/blog/getting-started.mdx +41 -0
- package/carbon/content/changelog/v0-1-0.mdx +40 -0
- package/carbon/content/docs/analytics.mdx +90 -0
- package/carbon/content/docs/authentication.mdx +231 -0
- package/carbon/content/docs/background-jobs.mdx +116 -0
- package/carbon/content/docs/cli.mdx +630 -0
- package/carbon/content/docs/database.mdx +236 -0
- package/carbon/content/docs/deployment.mdx +227 -0
- package/carbon/content/docs/development.mdx +238 -0
- package/carbon/content/docs/environments.mdx +84 -0
- package/carbon/content/docs/getting-started.mdx +112 -0
- package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
- package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
- package/carbon/content/docs/optional-services.mdx +160 -0
- package/carbon/db/Dockerfile +23 -0
- package/carbon/docker-compose.dev-init.yml +5 -0
- package/carbon/docker-compose.metabase.override.yml +14 -0
- package/carbon/docker-compose.metabase.prod.yml +28 -0
- package/carbon/docker-compose.metabase.yml +84 -0
- package/carbon/docker-compose.n8n.override.yml +14 -0
- package/carbon/docker-compose.n8n.prod.yml +31 -0
- package/carbon/docker-compose.n8n.yml +97 -0
- package/carbon/docker-compose.observability.override.yml +18 -0
- package/carbon/docker-compose.observability.prod.yml +28 -0
- package/carbon/docker-compose.observability.yml +125 -0
- package/carbon/docker-compose.override.yml +28 -0
- package/carbon/docker-compose.prod.yml +294 -0
- package/carbon/docker-compose.yml +508 -0
- package/carbon/docker-entrypoint.sh +12 -0
- package/carbon/ha/activate-standby.sh +52 -0
- package/carbon/ha/primary-init.sql +36 -0
- package/carbon/ha/standby-init.sh +74 -0
- package/carbon/k8s/LICENSE +99 -0
- package/carbon/k8s/README.md +272 -0
- package/carbon/k8s/base/app/deployment.yaml +130 -0
- package/carbon/k8s/base/app/hpa.yaml +44 -0
- package/carbon/k8s/base/app/kustomization.yaml +10 -0
- package/carbon/k8s/base/app/network-policy.yaml +124 -0
- package/carbon/k8s/base/app/pdb.yaml +14 -0
- package/carbon/k8s/base/app/rbac.yaml +36 -0
- package/carbon/k8s/base/app/service.yaml +16 -0
- package/carbon/k8s/base/backup/cronjob.yaml +120 -0
- package/carbon/k8s/base/backup/kustomization.yaml +7 -0
- package/carbon/k8s/base/backup/network-policy.yaml +31 -0
- package/carbon/k8s/base/backup/rbac.yaml +7 -0
- package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
- package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
- package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
- package/carbon/k8s/base/config/configmap.yaml +48 -0
- package/carbon/k8s/base/config/kustomization.yaml +8 -0
- package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
- package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
- package/carbon/k8s/base/kustomization.yaml +43 -0
- package/carbon/k8s/base/namespace.yaml +7 -0
- package/carbon/k8s/base/network-policies.yaml +95 -0
- package/carbon/k8s/base/registry/kustomization.yaml +5 -0
- package/carbon/k8s/base/registry/local-registry.yaml +193 -0
- package/carbon/k8s/base/traefik/certificate.yaml +21 -0
- package/carbon/k8s/base/traefik/configmap.yaml +13 -0
- package/carbon/k8s/base/traefik/deployment.yaml +165 -0
- package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
- package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
- package/carbon/k8s/base/traefik/middleware.yaml +109 -0
- package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
- package/carbon/k8s/base/traefik/service.yaml +38 -0
- package/carbon/k8s/flux/README.md +49 -0
- package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
- package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
- package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
- package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
- package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
- package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
- package/carbon/k8s/infra/kustomization.yaml +21 -0
- package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
- package/carbon/k8s/overlays/local/configmap.yaml +23 -0
- package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
- package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
- package/carbon/k8s/overlays/local/namespace.yaml +8 -0
- package/carbon/k8s/overlays/local/secrets.yaml +32 -0
- package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
- package/carbon/k8s/test-local.sh +318 -0
- package/carbon/k8s/values/supabase.values.yaml +133 -0
- package/carbon/package.json +154 -0
- package/carbon/runtime.Dockerfile +17 -0
- package/carbon/scripts/_dev-jwt.mjs +45 -0
- package/carbon/scripts/check-rls.ts +53 -0
- package/carbon/scripts/dev-init.js +191 -0
- package/carbon/scripts/dev.js +191 -0
- package/carbon/scripts/docker-down.js +63 -0
- package/carbon/scripts/docker-logs.js +59 -0
- package/carbon/scripts/docker-up.js +222 -0
- package/carbon/scripts/generate-dev-configs.sh +131 -0
- package/carbon/scripts/generate-rss.ts +116 -0
- package/carbon/scripts/generate-sitemap.ts +102 -0
- package/carbon/scripts/k8s-apply.js +71 -0
- package/carbon/scripts/k8s-delete.js +75 -0
- package/carbon/scripts/lib/manifest.js +176 -0
- package/carbon/scripts/secret-scan.mjs +278 -0
- package/carbon/scripts/validate-dev-configs.sh +101 -0
- package/carbon/src/client/App.tsx +202 -0
- package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
- package/carbon/src/client/assets/hyperformant-light.svg +29 -0
- package/carbon/src/client/assets/logos/aider.svg +1 -0
- package/carbon/src/client/assets/logos/antigravity.svg +1 -0
- package/carbon/src/client/assets/logos/bolt.svg +1 -0
- package/carbon/src/client/assets/logos/claude-code.svg +1 -0
- package/carbon/src/client/assets/logos/copilot.svg +1 -0
- package/carbon/src/client/assets/logos/cursor.svg +1 -0
- package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
- package/carbon/src/client/assets/logos/lovable.svg +1 -0
- package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
- package/carbon/src/client/assets/logos/v0.svg +1 -0
- package/carbon/src/client/assets/logos/windsurf.svg +1 -0
- package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
- package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
- package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
- package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
- package/carbon/src/client/components/AppSidebar.tsx +760 -0
- package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
- package/carbon/src/client/components/CTAFooter.tsx +59 -0
- package/carbon/src/client/components/ComparisonSection.tsx +132 -0
- package/carbon/src/client/components/ContentPanel.tsx +66 -0
- package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
- package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
- package/carbon/src/client/components/FAQSection.tsx +76 -0
- package/carbon/src/client/components/FileUpload.tsx +210 -0
- package/carbon/src/client/components/HeaderActions.tsx +17 -0
- package/carbon/src/client/components/Hero.tsx +608 -0
- package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
- package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
- package/carbon/src/client/components/Logo.tsx +87 -0
- package/carbon/src/client/components/LogoStrip.tsx +76 -0
- package/carbon/src/client/components/MetricsStrip.tsx +48 -0
- package/carbon/src/client/components/Nav.tsx +195 -0
- package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
- package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
- package/carbon/src/client/components/PageHeader.tsx +24 -0
- package/carbon/src/client/components/PlanGate.tsx +36 -0
- package/carbon/src/client/components/PricingSection.tsx +371 -0
- package/carbon/src/client/components/SEO.tsx +34 -0
- package/carbon/src/client/components/SmoothScroll.tsx +45 -0
- package/carbon/src/client/components/TechStackSection.tsx +165 -0
- package/carbon/src/client/components/WorkflowSection.tsx +604 -0
- package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
- package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
- package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
- package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
- package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
- package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
- package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
- package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
- package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
- package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
- package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
- package/carbon/src/client/components/effects/index.ts +2 -0
- package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
- package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
- package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
- package/carbon/src/client/components/scroll/index.ts +2 -0
- package/carbon/src/client/components/ui/accordion.tsx +71 -0
- package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
- package/carbon/src/client/components/ui/alert.tsx +73 -0
- package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
- package/carbon/src/client/components/ui/avatar.tsx +91 -0
- package/carbon/src/client/components/ui/badge.tsx +50 -0
- package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
- package/carbon/src/client/components/ui/button-group.tsx +78 -0
- package/carbon/src/client/components/ui/button.tsx +120 -0
- package/carbon/src/client/components/ui/calendar.tsx +182 -0
- package/carbon/src/client/components/ui/card.tsx +85 -0
- package/carbon/src/client/components/ui/carousel.tsx +227 -0
- package/carbon/src/client/components/ui/chart.tsx +357 -0
- package/carbon/src/client/components/ui/checkbox.tsx +27 -0
- package/carbon/src/client/components/ui/collapsible.tsx +15 -0
- package/carbon/src/client/components/ui/command.tsx +178 -0
- package/carbon/src/client/components/ui/context-menu.tsx +233 -0
- package/carbon/src/client/components/ui/dialog.tsx +132 -0
- package/carbon/src/client/components/ui/drawer.tsx +118 -0
- package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
- package/carbon/src/client/components/ui/empty.tsx +94 -0
- package/carbon/src/client/components/ui/field.tsx +226 -0
- package/carbon/src/client/components/ui/hover-card.tsx +44 -0
- package/carbon/src/client/components/ui/input-group.tsx +146 -0
- package/carbon/src/client/components/ui/input-otp.tsx +83 -0
- package/carbon/src/client/components/ui/input.tsx +20 -0
- package/carbon/src/client/components/ui/item.tsx +188 -0
- package/carbon/src/client/components/ui/kbd.tsx +26 -0
- package/carbon/src/client/components/ui/label.tsx +21 -0
- package/carbon/src/client/components/ui/menubar.tsx +250 -0
- package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
- package/carbon/src/client/components/ui/pagination.tsx +102 -0
- package/carbon/src/client/components/ui/popover.tsx +75 -0
- package/carbon/src/client/components/ui/progress.tsx +66 -0
- package/carbon/src/client/components/ui/radio-group.tsx +36 -0
- package/carbon/src/client/components/ui/resizable.tsx +46 -0
- package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
- package/carbon/src/client/components/ui/select.tsx +186 -0
- package/carbon/src/client/components/ui/separator.tsx +21 -0
- package/carbon/src/client/components/ui/sheet.tsx +124 -0
- package/carbon/src/client/components/ui/sidebar.tsx +702 -0
- package/carbon/src/client/components/ui/skeleton.tsx +13 -0
- package/carbon/src/client/components/ui/slider.tsx +57 -0
- package/carbon/src/client/components/ui/sonner.tsx +45 -0
- package/carbon/src/client/components/ui/spinner.tsx +15 -0
- package/carbon/src/client/components/ui/switch.tsx +30 -0
- package/carbon/src/client/components/ui/table.tsx +89 -0
- package/carbon/src/client/components/ui/tabs.tsx +73 -0
- package/carbon/src/client/components/ui/textarea.tsx +18 -0
- package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
- package/carbon/src/client/components/ui/toggle.tsx +44 -0
- package/carbon/src/client/components/ui/tooltip.tsx +56 -0
- package/carbon/src/client/hooks/api/index.ts +14 -0
- package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
- package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
- package/carbon/src/client/hooks/use-mobile.ts +21 -0
- package/carbon/src/client/hooks/useMousePosition.ts +91 -0
- package/carbon/src/client/hooks/useNotifications.tsx +124 -0
- package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
- package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
- package/carbon/src/client/hooks/usePackageManager.ts +69 -0
- package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
- package/carbon/src/client/hooks/useRunningServices.ts +114 -0
- package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
- package/carbon/src/client/index.css +467 -0
- package/carbon/src/client/index.html +56 -0
- package/carbon/src/client/lib/admin-services.ts +151 -0
- package/carbon/src/client/lib/api.ts +32 -0
- package/carbon/src/client/lib/blog.ts +35 -0
- package/carbon/src/client/lib/changelog.ts +33 -0
- package/carbon/src/client/lib/docs-search.ts +101 -0
- package/carbon/src/client/lib/docs.ts +37 -0
- package/carbon/src/client/lib/i18n.ts +32 -0
- package/carbon/src/client/lib/supabase.ts +72 -0
- package/carbon/src/client/lib/tailwind-colors.ts +357 -0
- package/carbon/src/client/lib/theme.ts +117 -0
- package/carbon/src/client/lib/utils.ts +22 -0
- package/carbon/src/client/locales/de.json +529 -0
- package/carbon/src/client/locales/en.json +461 -0
- package/carbon/src/client/locales/es.json +529 -0
- package/carbon/src/client/locales/fr.json +529 -0
- package/carbon/src/client/locales/pt.json +529 -0
- package/carbon/src/client/main.tsx +56 -0
- package/carbon/src/client/mdx.d.ts +13 -0
- package/carbon/src/client/pages/ApiDocs.tsx +76 -0
- package/carbon/src/client/pages/AuthCallback.tsx +34 -0
- package/carbon/src/client/pages/Blog.tsx +167 -0
- package/carbon/src/client/pages/Changelog.tsx +171 -0
- package/carbon/src/client/pages/Charts.tsx +388 -0
- package/carbon/src/client/pages/Checkout.tsx +227 -0
- package/carbon/src/client/pages/Contact.tsx +174 -0
- package/carbon/src/client/pages/Dashboard.tsx +368 -0
- package/carbon/src/client/pages/Docs.tsx +372 -0
- package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
- package/carbon/src/client/pages/Home.tsx +187 -0
- package/carbon/src/client/pages/Legal.tsx +100 -0
- package/carbon/src/client/pages/Login.tsx +408 -0
- package/carbon/src/client/pages/MFAVerify.tsx +156 -0
- package/carbon/src/client/pages/NotFound.tsx +21 -0
- package/carbon/src/client/pages/Onboarding.tsx +246 -0
- package/carbon/src/client/pages/ResetPassword.tsx +200 -0
- package/carbon/src/client/pages/UIComponents.tsx +390 -0
- package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
- package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
- package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
- package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
- package/carbon/src/client/pages/admin/Logs.tsx +18 -0
- package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
- package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
- package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
- package/carbon/src/client/pages/admin/Settings.tsx +314 -0
- package/carbon/src/client/pages/admin/Theme.tsx +465 -0
- package/carbon/src/client/pages/admin/Users.tsx +365 -0
- package/carbon/src/client/pages/api-docs.css +156 -0
- package/carbon/src/client/pages/organizations/Details.tsx +200 -0
- package/carbon/src/client/pages/organizations/Members.tsx +402 -0
- package/carbon/src/client/pages/settings/Billing.tsx +473 -0
- package/carbon/src/client/pages/settings/Profile.tsx +160 -0
- package/carbon/src/client/pages/settings/Security.tsx +341 -0
- package/carbon/src/client/public/favicon.svg +19 -0
- package/carbon/src/client/public/robots.txt +8 -0
- package/carbon/src/server/billing/index.ts +104 -0
- package/carbon/src/server/billing/provider.ts +81 -0
- package/carbon/src/server/billing/providers/paddle.ts +314 -0
- package/carbon/src/server/billing/providers/polar.ts +325 -0
- package/carbon/src/server/billing/providers/stripe.ts +233 -0
- package/carbon/src/server/emails/templates.ts +116 -0
- package/carbon/src/server/index.ts +554 -0
- package/carbon/src/server/lib/auth.ts +6 -0
- package/carbon/src/server/lib/email.ts +64 -0
- package/carbon/src/server/lib/env.ts +112 -0
- package/carbon/src/server/lib/errors.ts +21 -0
- package/carbon/src/server/lib/logger.ts +17 -0
- package/carbon/src/server/lib/rate-limiter.ts +288 -0
- package/carbon/src/server/lib/request.ts +34 -0
- package/carbon/src/server/lib/stripe.ts +42 -0
- package/carbon/src/server/lib/supabase.ts +65 -0
- package/carbon/src/server/middleware/requirePlan.ts +80 -0
- package/carbon/src/server/routes/_internal/services-status.ts +958 -0
- package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
- package/carbon/src/server/routes/health.ts +48 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
- package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
- package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
- package/carbon/src/server/routes/v1/auth.ts +390 -0
- package/carbon/src/server/routes/v1/billing.ts +718 -0
- package/carbon/src/server/routes/v1/contact.ts +93 -0
- package/carbon/src/server/routes/v1/index.ts +1333 -0
- package/carbon/src/server/routes/v1/newsletter.ts +181 -0
- package/carbon/src/server/routes/v1/performance.ts +157 -0
- package/carbon/src/server/routes/v1/stats.ts +170 -0
- package/carbon/src/server/routes/v1/theme.ts +106 -0
- package/carbon/src/server/routes/webhooks/billing.ts +376 -0
- package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
- package/carbon/src/server/types.ts +11 -0
- package/carbon/src/shared/pricing.ts +155 -0
- package/carbon/src/shared/types.ts +338 -0
- package/carbon/supabase/migrations/00001_init.sql +717 -0
- package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
- package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
- package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
- package/carbon/supabase/seed.sql +16 -0
- package/carbon/tests/_helpers/app.ts +45 -0
- package/carbon/tests/_helpers/env.ts +37 -0
- package/carbon/tests/_helpers/factories.ts +69 -0
- package/carbon/tests/_helpers/jwt.ts +23 -0
- package/carbon/tests/_helpers/setup-integration.ts +20 -0
- package/carbon/tests/_helpers/setup-rtl.ts +12 -0
- package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
- package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
- package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
- package/carbon/tests/integration/server/routes/health.test.ts +61 -0
- package/carbon/tests/structural/i18n-parity.test.ts +42 -0
- package/carbon/tests/unit/client/utils.test.ts +49 -0
- package/carbon/tests/unit/shared/pricing.test.ts +93 -0
- package/carbon/tsconfig.json +27 -0
- package/carbon/tsconfig.server.json +27 -0
- package/carbon/tsconfig.test.json +9 -0
- package/carbon/vite.config.ts +110 -0
- package/carbon/vitest.config.ts +74 -0
- package/carbon/volumes/db/jwt.sql +57 -0
- package/carbon/volumes/db/metabase-init.sh +29 -0
- package/carbon/volumes/db/n8n-init.sh +25 -0
- package/carbon/volumes/db/realtime.sql +33 -0
- package/carbon/volumes/db/roles.sql +93 -0
- package/carbon/volumes/db/set-passwords.sh +12 -0
- package/carbon/volumes/db/super-admin.dev.sql +113 -0
- package/carbon/volumes/db/super-admin.generated.sql +113 -0
- package/carbon/volumes/db/super-admin.sql +114 -0
- package/carbon/volumes/grafana/dashboards/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards/overview.json +523 -0
- package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
- package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
- package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
- package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
- package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
- package/carbon/volumes/kong/kong.yml +208 -0
- package/carbon/volumes/loki/loki-config.yml +58 -0
- package/carbon/volumes/n8n/hooks.js +131 -0
- package/carbon/volumes/n8n/scripts/setup.sh +66 -0
- package/carbon/volumes/prometheus/prometheus.yml +43 -0
- package/carbon/volumes/promtail/promtail-config.yml +64 -0
- package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
- package/carbon/volumes/traefik/middlewares.yml +95 -0
- package/carbon/volumes/traefik/vite-dev.yml +20 -0
- package/package.json +95 -0
- package/src/access.js +354 -0
- package/src/activate.js +187 -0
- package/src/add.js +718 -0
- package/src/backup.js +786 -0
- package/src/cli.js +350 -0
- package/src/configure.js +967 -0
- package/src/console.js +155 -0
- package/src/create.js +1499 -0
- package/src/deploy.js +311 -0
- package/src/destroy.js +2033 -0
- package/src/diagnose.js +735 -0
- package/src/down.js +80 -0
- package/src/failover.js +1032 -0
- package/src/lib/backup-s3.js +179 -0
- package/src/lib/build.js +33 -0
- package/src/lib/checksum.js +28 -0
- package/src/lib/ci-setup.js +666 -0
- package/src/lib/cli/help.js +129 -0
- package/src/lib/cli/parse-flags.js +160 -0
- package/src/lib/cli/select-action.js +65 -0
- package/src/lib/cli/select-environment.js +108 -0
- package/src/lib/cli/tty-guard.js +75 -0
- package/src/lib/cloudflare.js +447 -0
- package/src/lib/colors.js +52 -0
- package/src/lib/command.js +361 -0
- package/src/lib/config.js +359 -0
- package/src/lib/cost.js +103 -0
- package/src/lib/deploy/bundle.js +231 -0
- package/src/lib/deploy/compose/acme-verify.js +121 -0
- package/src/lib/deploy/compose/build-args.js +78 -0
- package/src/lib/deploy/compose/ha.js +1874 -0
- package/src/lib/deploy/compose/index.js +1294 -0
- package/src/lib/deploy/compose/reconcile.js +74 -0
- package/src/lib/deploy/github.js +382 -0
- package/src/lib/deploy/image.js +191 -0
- package/src/lib/deploy/index.js +119 -0
- package/src/lib/deploy/k8s/LICENSE +99 -0
- package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
- package/src/lib/deploy/k8s/ha/index.js +1000 -0
- package/src/lib/deploy/k8s/index.js +62 -0
- package/src/lib/deploy/k8s/k3s.js +2515 -0
- package/src/lib/deploy/orchestrator.js +1401 -0
- package/src/lib/deploy/prompts.js +545 -0
- package/src/lib/deploy/remote-build.js +130 -0
- package/src/lib/deploy/state.js +120 -0
- package/src/lib/deploy/utils.js +328 -0
- package/src/lib/deploy-logger.js +93 -0
- package/src/lib/dns-propagation.js +48 -0
- package/src/lib/environment.js +58 -0
- package/src/lib/fetch-retry.js +103 -0
- package/src/lib/github-environments.js +335 -0
- package/src/lib/hetzner-dns.js +377 -0
- package/src/lib/hetzner-guided-setup.js +275 -0
- package/src/lib/host-keys.js +37 -0
- package/src/lib/iac/cloud-init.js +52 -0
- package/src/lib/iac/index.js +325 -0
- package/src/lib/iac/programs/hetzner-compose.js +130 -0
- package/src/lib/iac/programs/hetzner-k8s.js +320 -0
- package/src/lib/kubectl.js +81 -0
- package/src/lib/licensing/index.js +259 -0
- package/src/lib/licensing/tiers.js +181 -0
- package/src/lib/licensing/validator.js +171 -0
- package/src/lib/merge-package-json.js +90 -0
- package/src/lib/operator-ip.js +381 -0
- package/src/lib/package-manager.js +111 -0
- package/src/lib/perf.js +71 -0
- package/src/lib/project-guard.js +39 -0
- package/src/lib/project.js +334 -0
- package/src/lib/providers/base.js +276 -0
- package/src/lib/providers/hetzner-s3.js +656 -0
- package/src/lib/providers/hetzner.js +755 -0
- package/src/lib/providers/index.js +164 -0
- package/src/lib/s3.js +510 -0
- package/src/lib/secret-scan.js +583 -0
- package/src/lib/secrets.js +63 -0
- package/src/lib/server-types.js +195 -0
- package/src/lib/shell.js +91 -0
- package/src/lib/ssh.js +241 -0
- package/src/lib/tracker.js +242 -0
- package/src/lib/upgrade-policy.js +170 -0
- package/src/lib/validators.js +105 -0
- package/src/lib/version.js +5 -0
- package/src/remove.js +292 -0
- package/src/reset.js +97 -0
- package/src/restore.js +871 -0
- package/src/scale.js +1734 -0
- package/src/shell.js +222 -0
- package/src/status.js +981 -0
- package/src/up.js +264 -0
- package/src/upgrade.js +721 -0
package/carbon/AGENTS.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working on this codebase. The **Security Rules** section below is mandatory — violations introduce real vulnerabilities.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- **Frontend**: React 19, Vite, React Router, TanStack Query, Shadcn UI, Tailwind CSS v4, i18next
|
|
8
|
+
- **Backend**: Hono (lightweight Node.js framework), Pino logger, Zod validation, nodemailer (SMTP email)
|
|
9
|
+
- **Auth/Database**: Self-hosted Supabase (PostgreSQL, Auth, REST API, Realtime, Storage)
|
|
10
|
+
- **Billing**: Stripe (subscriptions, checkout, webhooks, plan gating)
|
|
11
|
+
- **Content**: MDX (blog, changelog, docs) with remark/rehype plugins
|
|
12
|
+
- **Tooling**: Biome (lint/format), TypeScript, esbuild, Vitest
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Development
|
|
18
|
+
pnpm dev:start # Full cold start: Docker + migrations + dev servers
|
|
19
|
+
pnpm dev # Start API (port 3000) and Vite (port 5173)
|
|
20
|
+
pnpm dev:reset # Remove containers, volumes, and built images
|
|
21
|
+
|
|
22
|
+
# Build & Test
|
|
23
|
+
pnpm build # Build client and server
|
|
24
|
+
pnpm lint # Biome linting
|
|
25
|
+
pnpm typecheck # TypeScript type checking
|
|
26
|
+
pnpm test # Run every tier (unit + component + integration)
|
|
27
|
+
pnpm test:unit # Pure functions, validators, shared lib
|
|
28
|
+
pnpm test:component # React components + hooks (RTL + jsdom)
|
|
29
|
+
pnpm test:integration # Hono routes with mocked Supabase/Stripe/SMTP
|
|
30
|
+
pnpm test:watch # Vitest watch mode
|
|
31
|
+
pnpm test:coverage # Generate coverage/ report (v8)
|
|
32
|
+
pnpm test:prepush # What the pre-push git hook runs
|
|
33
|
+
|
|
34
|
+
# Docker
|
|
35
|
+
pnpm docker:up # Start Supabase services
|
|
36
|
+
pnpm docker:down # Stop services
|
|
37
|
+
pnpm docker:reset # Remove containers, volumes, and built images
|
|
38
|
+
pnpm db:migrate # Run SQL migrations
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
### Directory Structure
|
|
44
|
+
```
|
|
45
|
+
src/
|
|
46
|
+
├── client/ # React SPA (Vite dev server on :5173)
|
|
47
|
+
│ ├── components/
|
|
48
|
+
│ │ ├── ui/ # 50+ Shadcn UI components
|
|
49
|
+
│ │ └── auth/ # AuthProvider, ProtectedRoute
|
|
50
|
+
│ ├── hooks/ # Custom React hooks
|
|
51
|
+
│ ├── pages/ # Route pages (admin/, settings/, organizations/)
|
|
52
|
+
│ ├── lib/ # supabase.ts, i18n.ts, utils.ts, blog/changelog/docs loaders
|
|
53
|
+
│ └── locales/ # i18n translation files
|
|
54
|
+
├── server/ # Hono API (Node.js on :3000)
|
|
55
|
+
│ ├── index.ts # App entry, middleware, route mounting
|
|
56
|
+
│ ├── routes/ # health.ts, v1/, webhooks/, _internal/
|
|
57
|
+
│ ├── emails/ # React Email templates
|
|
58
|
+
│ └── lib/ # supabase.ts, env.ts, email.ts, stripe.ts, logger.ts, rate-limiter.ts
|
|
59
|
+
├── shared/ # Shared TypeScript types (types.ts, pricing.ts)
|
|
60
|
+
content/ # MDX content (blog/, changelog/, docs/)
|
|
61
|
+
supabase/migrations/ # SQL migrations (run with pnpm db:migrate)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### API Routes
|
|
65
|
+
|
|
66
|
+
| Route | Description |
|
|
67
|
+
|-------|-------------|
|
|
68
|
+
| `GET /api/health` | Liveness probe (no DB check, public) |
|
|
69
|
+
| `GET /api/health/ready` | Readiness probe (checks DB connectivity, public) |
|
|
70
|
+
| `POST /api/v1/auth/login` | Email/password login with lockout protection |
|
|
71
|
+
| `GET /api/v1/auth/settings` | Auth settings (enabled providers, MFA config) |
|
|
72
|
+
| `GET /api/v1/me` | Current user info with memberships |
|
|
73
|
+
| `GET/POST /api/v1/organizations` | List/create organizations |
|
|
74
|
+
| `GET/POST /api/v1/organizations/:orgId/members` | Manage members |
|
|
75
|
+
| `PATCH /api/v1/organizations/:orgId/members/:userId` | Update member role |
|
|
76
|
+
| `GET /api/v1/notifications` | User's active notifications |
|
|
77
|
+
| `POST /api/v1/notifications/:id/dismiss` | Dismiss notification |
|
|
78
|
+
| `POST /api/v1/billing/checkout` | Create checkout session (Stripe/Paddle/Polar) |
|
|
79
|
+
| `POST /api/v1/billing/portal` | Create customer portal session |
|
|
80
|
+
| `POST /api/v1/contact/submit` | Submit contact form (public, rate-limited) |
|
|
81
|
+
| `POST /api/v1/newsletter/subscribe` | Subscribe to newsletter (public, rate-limited) |
|
|
82
|
+
| `GET /api/v1/newsletter/confirm` | Confirm newsletter subscription (via email token) |
|
|
83
|
+
| `GET /api/v1/newsletter/unsubscribe` | Unsubscribe from newsletter |
|
|
84
|
+
| `GET /api/v1/admin/users` | List all users (super admin) |
|
|
85
|
+
| `GET /api/v1/admin/organizations` | List all organizations (super admin) |
|
|
86
|
+
| `POST /api/v1/admin/impersonate/:userId` | Impersonate user (super admin) |
|
|
87
|
+
| `GET/POST /api/v1/admin/notifications` | Manage notifications (super admin) |
|
|
88
|
+
| `GET /api/v1/admin/stats` | Platform-wide stats (super admin) |
|
|
89
|
+
| `GET /api/v1/admin/performance` | Service performance/health checks (super admin) |
|
|
90
|
+
| `GET /api/v1/admin/jobs` | Background jobs and execution history (super admin) |
|
|
91
|
+
| `POST /api/v1/admin/jobs/trigger` | Manually trigger a background job (super admin) |
|
|
92
|
+
| `GET/PATCH/DELETE /api/v1/admin/contact` | Manage contact submissions (super admin) |
|
|
93
|
+
| `GET /api/v1/admin/newsletter` | Manage newsletter subscribers (super admin) |
|
|
94
|
+
| `POST /api/v1/admin/newsletter/send` | Send newsletter to active subscribers (super admin) |
|
|
95
|
+
| `GET /api/v1/admin/newsletter/export` | Export subscribers as CSV (super admin) |
|
|
96
|
+
| `GET /api/_internal/verify-role` | Traefik ForwardAuth |
|
|
97
|
+
| `POST /api/webhooks/billing` | Billing webhook handler (Stripe/Paddle/Polar) |
|
|
98
|
+
| `POST /api/webhooks/stripe` | Stripe webhook handler (backward compat) |
|
|
99
|
+
| `GET /api/docs` | Scalar API reference (dev only) |
|
|
100
|
+
|
|
101
|
+
### Authentication Flow
|
|
102
|
+
|
|
103
|
+
1. **Client-side**: Supabase JS handles auth (email/password, OAuth, magic links, MFA)
|
|
104
|
+
2. **API requests**: Client sends JWT in `Authorization: Bearer <token>` header
|
|
105
|
+
3. **Server middleware**: Extracts token, calls `supabase.auth.getUser()`, sets `c.get('user')`
|
|
106
|
+
4. **RLS**: Database queries respect row-level security based on authenticated user
|
|
107
|
+
|
|
108
|
+
## Supabase Client Usage
|
|
109
|
+
|
|
110
|
+
This is the #1 source of bugs. Three clients exist:
|
|
111
|
+
|
|
112
|
+
**Client-side** (`src/client/lib/supabase.ts`) — uses anon key, RLS enforced:
|
|
113
|
+
```typescript
|
|
114
|
+
import { supabase } from '@/lib/supabase';
|
|
115
|
+
const { data } = await supabase.from('organizations').select('*'); // RLS enforced
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Server-side** (`src/server/lib/supabase.ts`) — two options:
|
|
119
|
+
```typescript
|
|
120
|
+
// User-context queries (respects RLS) — use for most routes
|
|
121
|
+
const supabase = c.get('supabase');
|
|
122
|
+
|
|
123
|
+
// Admin operations (bypasses RLS) — use only when necessary
|
|
124
|
+
import { supabaseAdmin } from '@/server/lib/supabase';
|
|
125
|
+
|
|
126
|
+
// Auth operations — NEVER use supabaseAdmin for signInWithPassword
|
|
127
|
+
// (stores session state on singleton, contaminates subsequent queries)
|
|
128
|
+
import { createAuthClient } from '@/server/lib/supabase';
|
|
129
|
+
const authClient = createAuthClient();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Key Patterns
|
|
133
|
+
|
|
134
|
+
### Adding an API Endpoint
|
|
135
|
+
|
|
136
|
+
1. Create route in `src/server/routes/v1/` — follow existing routes for the `Variables` type, Zod validation, and auth check pattern
|
|
137
|
+
2. Mount in `src/server/index.ts`: `app.route('/api/v1/items', myRoutes)`
|
|
138
|
+
|
|
139
|
+
### Adding a Page
|
|
140
|
+
|
|
141
|
+
1. Create component in `src/client/pages/` — use `SidebarProvider` + `AppSidebar` + `SidebarInset` layout (see existing pages)
|
|
142
|
+
2. Add route in `src/client/App.tsx` wrapped in `<ProtectedRoute>`
|
|
143
|
+
|
|
144
|
+
### Adding a Database Table
|
|
145
|
+
|
|
146
|
+
1. Create migration in `supabase/migrations/` with:
|
|
147
|
+
- Table with `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`, `created_at`, `updated_at`
|
|
148
|
+
- Index on foreign key columns
|
|
149
|
+
- `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`
|
|
150
|
+
- RLS policies using `auth.uid()` for user-scoped access (see `00001_init.sql` for patterns)
|
|
151
|
+
- `update_updated_at()` trigger
|
|
152
|
+
2. Run `pnpm db:migrate` and add types to `src/shared/types.ts`
|
|
153
|
+
|
|
154
|
+
### Data Fetching (TanStack Query)
|
|
155
|
+
|
|
156
|
+
**NEVER use raw `fetch` in `useEffect`** — always use `useQuery` for reads and `useMutation` for writes. Raw fetch in useEffect bypasses TanStack Query's deduplication, caching, and retry logic, and React StrictMode will double-fire it in development.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// CORRECT: useQuery with auth guard
|
|
160
|
+
const { data, isLoading } = useQuery({
|
|
161
|
+
queryKey: ['items', orgId],
|
|
162
|
+
queryFn: async () => {
|
|
163
|
+
const headers = await getAuthHeaders();
|
|
164
|
+
const res = await fetch(`/api/v1/items?orgId=${orgId}`, { headers });
|
|
165
|
+
if (!res.ok) throw new Error('Failed to fetch items');
|
|
166
|
+
return res.json();
|
|
167
|
+
},
|
|
168
|
+
enabled: !!user && !!orgId, // Skip fetch when unauthenticated or missing params
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// WRONG: raw fetch in useEffect
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
fetch('/api/v1/items').then(r => r.json()).then(setItems);
|
|
174
|
+
}, []);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Rules:
|
|
178
|
+
- **Gate authenticated queries** with `enabled: !!user` (or `!!session`) to prevent 401s for logged-out users
|
|
179
|
+
- **Use `getAuthHeaders()`** from `@/lib/api.ts` for auth headers — never duplicate it locally, never send empty `Authorization` headers
|
|
180
|
+
- **Place reusable hooks** in `src/client/hooks/api/` — page-specific queries can live in the page file
|
|
181
|
+
- **Invalidate related queries** after mutations: `queryClient.invalidateQueries({ queryKey: ['items'] })`
|
|
182
|
+
- **Set `staleTime`** to control cache freshness (default is 5min via QueryClient config)
|
|
183
|
+
|
|
184
|
+
## Database Schema
|
|
185
|
+
|
|
186
|
+
| Table | Purpose |
|
|
187
|
+
|-------|---------|
|
|
188
|
+
| `organizations` | Multi-tenant organizations (name, slug, plan) |
|
|
189
|
+
| `memberships` | User-org relationships (role: OWNER, ADMIN, MEMBER) |
|
|
190
|
+
| `customers` | Billing customer records (user or org, any provider) |
|
|
191
|
+
| `subscriptions` | Billing subscription data |
|
|
192
|
+
| `notifications` | System-wide or org-specific notifications |
|
|
193
|
+
| `notification_dismissals` | Track dismissed notifications per user |
|
|
194
|
+
| `failed_login_attempts` | Brute force protection |
|
|
195
|
+
| `app_settings` | Global app configuration |
|
|
196
|
+
| `cron_job_history` | Background job execution history (pg_cron) |
|
|
197
|
+
| `contact_submissions` | Contact form submissions (name, email, subject, message, status) |
|
|
198
|
+
| `newsletter_subscribers` | Newsletter subscribers (email, status: pending/active/unsubscribed) |
|
|
199
|
+
|
|
200
|
+
RLS helper functions: `get_user_org_ids()`, `get_user_admin_org_ids()`, `is_super_admin()`
|
|
201
|
+
|
|
202
|
+
## Path Aliases
|
|
203
|
+
|
|
204
|
+
- `@/*` resolves to `./src/client/*`
|
|
205
|
+
- `@shared/*` resolves to `./src/shared/*`
|
|
206
|
+
- `@server/*` resolves to `./src/server/*` (test-only — server code uses relative imports)
|
|
207
|
+
- **Never use `@/shared/*`** — it won't resolve
|
|
208
|
+
|
|
209
|
+
## Testing
|
|
210
|
+
|
|
211
|
+
Three tiers — pick the narrowest one that exercises the regression you're worried about.
|
|
212
|
+
|
|
213
|
+
| Tier | Env | What it covers | Where it lives |
|
|
214
|
+
|---|---|---|---|
|
|
215
|
+
| `unit` | node | Pure functions, validators, pricing/format/business helpers, structural invariants (e.g. i18n parity) | `tests/unit/`, `tests/structural/` |
|
|
216
|
+
| `component` | jsdom | React components + custom hooks via React Testing Library | `tests/component/` |
|
|
217
|
+
| `integration` | node | Hono route handlers end-to-end via `app.request()`, with Supabase/Stripe/SMTP mocked at the module boundary | `tests/integration/` |
|
|
218
|
+
|
|
219
|
+
Helpers live in `tests/_helpers/`:
|
|
220
|
+
|
|
221
|
+
- `app.ts` — `mountRoute(prefix, routes)` to build a tiny Hono app for one route; `jsonPost(body)` for request boilerplate.
|
|
222
|
+
- `factories.ts` — `makeUser`, `makeOrg`, `makeContactSubmission` for plausible domain fixtures.
|
|
223
|
+
- `jwt.ts` — `mockJwt(payload)` for HS256-signed test tokens.
|
|
224
|
+
- `env.ts` — `mockEnv({...})` returning a restore closure (no globals).
|
|
225
|
+
- `setup-rtl.ts` — vitest setup for the component project (jest-dom matchers + RTL cleanup).
|
|
226
|
+
- `setup-integration.ts` — seeds plausible Supabase env values so server modules load without zod-validation errors.
|
|
227
|
+
|
|
228
|
+
**Mocking conventions:**
|
|
229
|
+
- Supabase: `vi.mock('@server/lib/supabase', () => ({ supabaseAdmin: { from: () => ({ insert: vi.fn() }) } }))`.
|
|
230
|
+
- Stripe: `vi.mock('@server/lib/stripe', () => ({ stripe: { ... } }))`.
|
|
231
|
+
- fetch (client side): `vi.stubGlobal('fetch', vi.fn(...))` + `vi.unstubAllGlobals()` in afterEach.
|
|
232
|
+
- TanStack Query: wrap with a fresh `QueryClient` per render (`retry: false`) — see `tests/component/use-auth-settings.test.tsx`.
|
|
233
|
+
- Rate limiter: `vi.mock('@server/lib/rate-limiter', () => ({ createRateLimiter: () => async (_c, next) => { await next(); } }))` — the real module starts a 60s setInterval at module load.
|
|
234
|
+
|
|
235
|
+
See `TESTING.md` for the full guide. The `test-maintainer` agent reads the tier directories to learn project conventions and will follow them automatically.
|
|
236
|
+
|
|
237
|
+
## Agent Orchestration
|
|
238
|
+
|
|
239
|
+
This project uses Claude Code's agent teams with a **lead-coordinator** that orchestrates 4 specialist teammates:
|
|
240
|
+
|
|
241
|
+
| Agent | Role | Quality Gate |
|
|
242
|
+
|-------|------|-------------|
|
|
243
|
+
| `lead-coordinator` | Decomposes tasks, delegates, synthesizes results | — |
|
|
244
|
+
| `backend-engineer` | Backend APIs, database, Docker/K8s, server logic | `pnpm lint` + `pnpm typecheck` on idle |
|
|
245
|
+
| `frontend-engineer` | Frontend pages, components, layouts, styling | `pnpm lint` + `pnpm typecheck` on idle |
|
|
246
|
+
| `security-reviewer` | Security audit after backend/infra changes | — |
|
|
247
|
+
| `test-maintainer` | Test writing after any meaningful code change | `pnpm test:unit` on task complete |
|
|
248
|
+
|
|
249
|
+
**Typical workflow**: Use the lead-coordinator for complex multi-step tasks. It decomposes the work, spawns specialists in dependency order (migrations -> API -> security review -> frontend -> tests), and synthesizes results. For simple single-domain tasks, invoke the specialist directly.
|
|
250
|
+
|
|
251
|
+
**Quality gates** are enforced via hooks in `.claude/hooks/`:
|
|
252
|
+
- `teammate-idle-gate.sh` — lint + typecheck before backend/frontend engineers go idle
|
|
253
|
+
- `task-completed-gate.sh` — unit tests must pass before QA marks a task complete
|
|
254
|
+
|
|
255
|
+
**Configuration**: `.claude/settings.json` registers the hooks and the `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` env var required for agent teams.
|
|
256
|
+
|
|
257
|
+
## Important Notes
|
|
258
|
+
|
|
259
|
+
- **Prefer existing patterns** — follow the established code style in existing routes/components
|
|
260
|
+
- **Auth is handled by Supabase** — don't implement custom auth, use `supabase.auth.*` methods
|
|
261
|
+
- **Add Shadcn components** with `pnpm dlx shadcn@latest add [component-name]`
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
# Security Rules (Mandatory)
|
|
266
|
+
|
|
267
|
+
The rules below are mandatory — violations introduce real vulnerabilities. The three most critical:
|
|
268
|
+
|
|
269
|
+
1. **Always check auth** — every endpoint must verify `c.get('user')` and return 401 if null
|
|
270
|
+
2. **Always validate input** — use Zod schemas for all API request bodies before processing
|
|
271
|
+
3. **Use `c.get('supabase')` for user data** — never use `supabaseAdmin` for queries shown to end users; it bypasses RLS
|
|
272
|
+
|
|
273
|
+
## Database Security (RLS)
|
|
274
|
+
|
|
275
|
+
- **MUST** enable RLS on every new table: `ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;`
|
|
276
|
+
- **MUST** use `(SELECT auth.uid())` in RLS policies, not bare `auth.uid()` — the subquery form prevents the query planner from re-evaluating per row
|
|
277
|
+
- **MUST** use `SECURITY DEFINER` + `SET search_path = ''` on all helper functions used in RLS policies
|
|
278
|
+
- **NEVER** disable RLS on existing tables
|
|
279
|
+
- **NEVER** use `USING (true)` as a policy for user data — every table needs real ownership checks
|
|
280
|
+
- **NEVER** grant direct table access to `anon` or `authenticated` roles beyond what RLS policies allow
|
|
281
|
+
|
|
282
|
+
### RLS Helper Functions
|
|
283
|
+
|
|
284
|
+
Use these existing functions in policies instead of writing inline queries:
|
|
285
|
+
|
|
286
|
+
| Function | Returns | Use When |
|
|
287
|
+
|----------|---------|----------|
|
|
288
|
+
| `get_user_org_ids()` | `SETOF UUID` | User needs access to any org they belong to |
|
|
289
|
+
| `get_user_admin_org_ids()` | `SETOF UUID` | Only OWNER or ADMIN roles should have access |
|
|
290
|
+
| `get_user_owner_org_ids()` | `SETOF UUID` | Only OWNER role should have access |
|
|
291
|
+
| `get_user_customer_ids()` | `SETOF UUID` | Billing data — includes personal + org customers |
|
|
292
|
+
| `is_super_admin()` | `BOOLEAN` | Platform-wide admin operations |
|
|
293
|
+
|
|
294
|
+
### RLS Policy Pattern
|
|
295
|
+
|
|
296
|
+
```sql
|
|
297
|
+
-- Correct: subquery form
|
|
298
|
+
CREATE POLICY "Users can view own data"
|
|
299
|
+
ON my_table FOR SELECT
|
|
300
|
+
USING (user_id = (SELECT auth.uid()));
|
|
301
|
+
|
|
302
|
+
-- Correct: using helper function
|
|
303
|
+
CREATE POLICY "Users can view org data"
|
|
304
|
+
ON my_table FOR SELECT
|
|
305
|
+
USING (organization_id IN (SELECT get_user_org_ids()));
|
|
306
|
+
|
|
307
|
+
-- WRONG: bare function call (performance issue)
|
|
308
|
+
CREATE POLICY "bad_policy"
|
|
309
|
+
ON my_table FOR SELECT
|
|
310
|
+
USING (user_id = auth.uid());
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## API Route Security
|
|
314
|
+
|
|
315
|
+
- **MUST** check authentication on every endpoint: `const user = c.get('user'); if (!user) return c.json({ error: 'Unauthorized' }, 401);`
|
|
316
|
+
- **MUST** validate all input with Zod schemas before processing
|
|
317
|
+
- **MUST** use `c.get('supabase')` for user-facing database queries — this client respects RLS
|
|
318
|
+
- **MUST** validate redirect URLs against an allowlist — never redirect to arbitrary user-supplied URLs
|
|
319
|
+
- **MUST** use `createAuthClient()` for `signInWithPassword()` operations — never use the `supabaseAdmin` singleton for auth flows (it stores session state and contaminates subsequent queries)
|
|
320
|
+
- **MUST** verify webhook signatures before processing webhook payloads (Stripe uses `stripe.webhooks.constructEvent()`)
|
|
321
|
+
- **NEVER** use `supabaseAdmin` for data shown to end users — it bypasses RLS
|
|
322
|
+
- **NEVER** return raw database errors to clients — always return a generic error message
|
|
323
|
+
|
|
324
|
+
### Supabase Client Selection
|
|
325
|
+
|
|
326
|
+
| Client | Import / Access | RLS | Use For |
|
|
327
|
+
|--------|----------------|-----|---------|
|
|
328
|
+
| `c.get('supabase')` | Hono context (middleware-injected) | Enforced | All user-facing queries |
|
|
329
|
+
| `supabaseAdmin` | `import { supabaseAdmin } from '@/server/lib/supabase'` | Bypassed | Admin operations, webhooks, background jobs |
|
|
330
|
+
| `createAuthClient()` | `import { createAuthClient } from '@/server/lib/supabase'` | N/A | `signInWithPassword()` and other auth flows |
|
|
331
|
+
|
|
332
|
+
### API Endpoint Pattern
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
myRoutes.post('/', async (c) => {
|
|
336
|
+
// 1. Auth check
|
|
337
|
+
const user = c.get('user');
|
|
338
|
+
if (!user) return c.json({ error: 'Unauthorized' }, 401);
|
|
339
|
+
|
|
340
|
+
// 2. Input validation
|
|
341
|
+
const body = await c.req.json();
|
|
342
|
+
const result = schema.safeParse(body);
|
|
343
|
+
if (!result.success) {
|
|
344
|
+
return c.json({ error: result.error.issues.map(e => e.message).join(', ') }, 400);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 3. Use RLS-enforced client
|
|
348
|
+
const supabase = c.get('supabase');
|
|
349
|
+
const { data, error } = await supabase.from('items').insert(result.data).select().single();
|
|
350
|
+
|
|
351
|
+
// 4. Generic error response
|
|
352
|
+
if (error) return c.json({ error: 'Failed to create item' }, 500);
|
|
353
|
+
return c.json({ item: data }, 201);
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Privileged Services
|
|
358
|
+
|
|
359
|
+
- **MUST** protect Studio, n8n, Metabase, and Grafana with Traefik ForwardAuth in production
|
|
360
|
+
- **MUST** use Docker socket proxy in production — never mount `/var/run/docker.sock` directly into Traefik
|
|
361
|
+
- **NEVER** expose internal Supabase services (Auth, PostgREST, Realtime, Storage) directly — they must go through Kong
|
|
362
|
+
- **NEVER** remove ForwardAuth middleware from production compose files or Kubernetes manifests
|
|
363
|
+
|
|
364
|
+
### Adding a New Admin Service
|
|
365
|
+
|
|
366
|
+
1. Add Traefik ForwardAuth label in the service's compose config:
|
|
367
|
+
```yaml
|
|
368
|
+
labels:
|
|
369
|
+
- "traefik.http.routers.myservice.middlewares=super-admin-auth@file"
|
|
370
|
+
```
|
|
371
|
+
2. Create a dev override file that disables the middleware for local development:
|
|
372
|
+
```yaml
|
|
373
|
+
labels:
|
|
374
|
+
- "traefik.http.routers.myservice.middlewares="
|
|
375
|
+
```
|
|
376
|
+
3. Use `Host(\`myservice.localhost\`)` for the routing rule
|
|
377
|
+
|
|
378
|
+
### ForwardAuth Middleware Reference
|
|
379
|
+
|
|
380
|
+
| Middleware | Required Role | Used By |
|
|
381
|
+
|-----------|--------------|---------|
|
|
382
|
+
| `admin-auth` | `super_admin` | Studio, n8n, Metabase, Grafana (Kubernetes) |
|
|
383
|
+
| `super-admin-auth` | `super_admin` | Studio, n8n, Metabase, Grafana (Docker Compose) |
|
|
384
|
+
|
|
385
|
+
## Environment & Secrets
|
|
386
|
+
|
|
387
|
+
- **NEVER** commit `.env`, `.env.local`, or any file containing secrets
|
|
388
|
+
- **NEVER** hardcode secrets, API keys, or passwords in source code
|
|
389
|
+
- **MUST** use the `VITE_` prefix for any environment variable that needs client-side access
|
|
390
|
+
- **NEVER** expose `SUPABASE_SERVICE_ROLE_KEY` to the client — it bypasses all RLS
|
|
391
|
+
- **MUST** validate all server environment variables in `src/server/lib/env.ts`
|
|
392
|
+
|
|
393
|
+
## Rate Limiting
|
|
394
|
+
|
|
395
|
+
Three tiers are configured in `src/server/index.ts`:
|
|
396
|
+
|
|
397
|
+
| Route Pattern | Limit | Reason |
|
|
398
|
+
|--------------|-------|--------|
|
|
399
|
+
| `/api/v1/*` | 100/min per IP | Standard API endpoints |
|
|
400
|
+
| `/api/webhooks/*` | 500/min per IP | External services (Stripe) send bursts |
|
|
401
|
+
| `/api/_internal/*` | 1000/min per IP | Infrastructure calls (Traefik ForwardAuth) |
|
|
402
|
+
|
|
403
|
+
When adding new route groups, assign an appropriate rate limit tier.
|
|
404
|
+
|
|
405
|
+
## Infrastructure (Kubernetes)
|
|
406
|
+
|
|
407
|
+
- **MUST** use deny-all NetworkPolicy as the default — explicitly allowlist each communication path
|
|
408
|
+
- **MUST** set pod security context on all new deployments:
|
|
409
|
+
```yaml
|
|
410
|
+
securityContext:
|
|
411
|
+
runAsNonRoot: true
|
|
412
|
+
runAsUser: 1000
|
|
413
|
+
runAsGroup: 1000
|
|
414
|
+
readOnlyRootFilesystem: true
|
|
415
|
+
allowPrivilegeEscalation: false
|
|
416
|
+
capabilities:
|
|
417
|
+
drop:
|
|
418
|
+
- ALL
|
|
419
|
+
```
|
|
420
|
+
- **MUST** use `emptyDir` volumes for any writable paths (e.g., `/tmp`)
|
|
421
|
+
- **NEVER** run application containers as root (init containers are the only exception)
|
|
422
|
+
- **NEVER** add capabilities — drop ALL and don't add any back
|
package/carbon/CLAUDE.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Template Development Guide
|
|
2
|
+
|
|
3
|
+
This guide covers developing and testing the `carbon/` template directory locally.
|
|
4
|
+
|
|
5
|
+
## Template Variable System
|
|
6
|
+
|
|
7
|
+
The CLI replaces placeholders like `{{PROJECT_NAME}}` when users run `npx vibecarbon create`. However, these placeholders cause issues when testing locally because:
|
|
8
|
+
|
|
9
|
+
1. Services may reject special characters (`{}`) in identifiers
|
|
10
|
+
2. YAML parsers may fail on unquoted template syntax
|
|
11
|
+
3. Services expect actual values, not placeholder strings
|
|
12
|
+
|
|
13
|
+
### Solution: Dev File Pattern
|
|
14
|
+
|
|
15
|
+
Every config file with template placeholders needs a corresponding dev file:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
volumes/<service>/config.yml # Template with {{PLACEHOLDERS}}
|
|
19
|
+
volumes/<service>/config.dev.yml # Dev file with hardcoded values
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Override files mount dev configs during local development. The build system automatically loads them in the correct order:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
docker-compose.yml # Base services
|
|
26
|
+
docker-compose.{service}.yml # Optional service (e.g., observability)
|
|
27
|
+
docker-compose.{service}.override.yml # Service-specific dev overrides
|
|
28
|
+
docker-compose.override.yml # Main dev overrides (loaded last)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Important:** Services defined in optional compose files must have their dev overrides in a matching `{service}.override.yml` file, not the main override file. This prevents errors when running without that service.
|
|
32
|
+
|
|
33
|
+
## Adding a New Service
|
|
34
|
+
|
|
35
|
+
### Step 1: Create template config
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
# volumes/myservice/config.yml
|
|
39
|
+
name: {{PROJECT_NAME}}
|
|
40
|
+
api_key: {{MYSERVICE_KEY}}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Step 2: Generate dev config
|
|
44
|
+
|
|
45
|
+
Run the dev file generator:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
./scripts/generate-dev-configs.sh
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or manually create with hardcoded values:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
# volumes/myservice/config.dev.yml
|
|
55
|
+
name: vibecarbon
|
|
56
|
+
api_key: dev-key-not-for-production
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Step 3: Add override mount
|
|
60
|
+
|
|
61
|
+
Add the volume mount to the appropriate override file based on **which service** the volume is mounted to:
|
|
62
|
+
|
|
63
|
+
- If mounting to a service in `docker-compose.yml` (db, kong, etc.) → use `docker-compose.override.yml`
|
|
64
|
+
- If mounting to a service in `docker-compose.{feature}.yml` → use `docker-compose.{feature}.override.yml`
|
|
65
|
+
|
|
66
|
+
Example for a service in the base compose file:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
# docker-compose.override.yml
|
|
70
|
+
services:
|
|
71
|
+
db:
|
|
72
|
+
volumes:
|
|
73
|
+
- ./volumes/db/myservice-init.dev.sql:/docker-entrypoint-initdb.d/init-scripts/95-myservice.sql:Z
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Example for a service in an optional compose file:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
# docker-compose.observability.override.yml
|
|
80
|
+
services:
|
|
81
|
+
grafana:
|
|
82
|
+
volumes:
|
|
83
|
+
- ./volumes/grafana/config.dev.yml:/etc/grafana/config.yml:ro
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Step 4: Add placeholder to CLI
|
|
87
|
+
|
|
88
|
+
In `src/create.js`, add to the replacements map:
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
['{{MYSERVICE_KEY}}', generateSecret()],
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Step 5: Test both paths
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Test local development
|
|
98
|
+
pnpm dev:start:all
|
|
99
|
+
|
|
100
|
+
# Test CLI generation
|
|
101
|
+
cd .. && node src/cli.js create test-app -y \
|
|
102
|
+
--admin-email=test@example.com \
|
|
103
|
+
--admin-password=testpass123
|
|
104
|
+
cd test-app && pnpm dev:start
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Current Dev File Mappings
|
|
108
|
+
|
|
109
|
+
### Compose Override Files
|
|
110
|
+
|
|
111
|
+
| Service Compose | Dev Override | Notes |
|
|
112
|
+
|-----------------|--------------|-------|
|
|
113
|
+
| `docker-compose.yml` | `docker-compose.override.yml` | Core services (db, kong, traefik) |
|
|
114
|
+
| `docker-compose.observability.yml` | `docker-compose.observability.override.yml` | Grafana: volumes + no ForwardAuth |
|
|
115
|
+
| `docker-compose.n8n.yml` | `docker-compose.n8n.override.yml` | n8n: no ForwardAuth |
|
|
116
|
+
| `docker-compose.metabase.yml` | `docker-compose.metabase.override.yml` | Metabase: no ForwardAuth |
|
|
117
|
+
|
|
118
|
+
### Volume Config Files
|
|
119
|
+
|
|
120
|
+
| Template File | Dev File | Notes |
|
|
121
|
+
|--------------|----------|-------|
|
|
122
|
+
| `volumes/db/super-admin.sql` | `volumes/db/super-admin.dev.sql` | Admin credentials |
|
|
123
|
+
| `volumes/grafana/provisioning/dashboards/dashboards.yml` | `...dashboards.dev.yml` | Folder UIDs |
|
|
124
|
+
| `volumes/grafana/dashboards/*.json` | `volumes/grafana/dashboards.dev/*.json` | Dashboard UIDs |
|
|
125
|
+
| `volumes/traefik/middlewares.yml` | `volumes/traefik/middlewares.dev.yml` | ForwardAuth uses host.docker.internal |
|
|
126
|
+
|
|
127
|
+
## Automation
|
|
128
|
+
|
|
129
|
+
### Generate all dev configs from templates
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
./scripts/generate-dev-configs.sh
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This script:
|
|
136
|
+
- Finds all template files with `{{PROJECT_NAME}}`
|
|
137
|
+
- Generates corresponding `.dev` versions
|
|
138
|
+
- Replaces common placeholders with dev values
|
|
139
|
+
|
|
140
|
+
### Validate dev files exist
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
./scripts/validate-dev-configs.sh
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Run this in CI to ensure new template files have dev counterparts.
|
|
147
|
+
|
|
148
|
+
## Common Issues
|
|
149
|
+
|
|
150
|
+
### Container not picking up new mounts
|
|
151
|
+
|
|
152
|
+
After modifying `docker-compose.override.yml`, recreate the container:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Wrong - doesn't apply new mounts
|
|
156
|
+
docker compose restart myservice
|
|
157
|
+
|
|
158
|
+
# Correct - recreates with new mounts
|
|
159
|
+
docker compose up -d myservice
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Service rejecting config
|
|
163
|
+
|
|
164
|
+
Check if the service has restrictions on:
|
|
165
|
+
- Special characters in identifiers/UIDs
|
|
166
|
+
- Required fields that can't be template placeholders
|
|
167
|
+
- Strict YAML parsing that rejects `{{}}`
|
|
168
|
+
|
|
169
|
+
### Template works but dev fails (or vice versa)
|
|
170
|
+
|
|
171
|
+
Ensure both files have identical structure - only values should differ.
|
|
172
|
+
|
|
173
|
+
### Admin services (n8n, Metabase, Grafana) in development
|
|
174
|
+
|
|
175
|
+
In production, these services use Traefik ForwardAuth to require `super_admin` role. In development, ForwardAuth is disabled via service-specific override files:
|
|
176
|
+
|
|
177
|
+
- `docker-compose.n8n.override.yml`
|
|
178
|
+
- `docker-compose.metabase.override.yml`
|
|
179
|
+
- `docker-compose.observability.override.yml`
|
|
180
|
+
|
|
181
|
+
These override files set `traefik.http.routers.{service}.middlewares=` (empty) to remove the ForwardAuth requirement. Docker Compose labels are **additive**, so you must explicitly set the middleware label to empty to override it.
|
|
182
|
+
|
|
183
|
+
**If you get 401 errors:**
|
|
184
|
+
|
|
185
|
+
1. Ensure the override files are included in your compose command
|
|
186
|
+
2. Force recreate the containers: `docker compose ... up -d --force-recreate n8n metabase grafana`
|
|
187
|
+
3. Verify the router has no middlewares: `curl http://traefik.localhost/api/http/routers/n8n@docker | jq .middlewares`
|