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.
Files changed (495) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +188 -0
  3. package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
  4. package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
  5. package/carbon/.claude/settings.json +29 -0
  6. package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
  7. package/carbon/.dockerignore +57 -0
  8. package/carbon/.env.example +219 -0
  9. package/carbon/.github/copilot-instructions.md +3 -0
  10. package/carbon/.github/workflows/deploy.yml +346 -0
  11. package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
  12. package/carbon/.windsurfrules +74 -0
  13. package/carbon/AGENTS.md +422 -0
  14. package/carbon/CLAUDE.md +3 -0
  15. package/carbon/DEVELOPMENT.md +187 -0
  16. package/carbon/Dockerfile +98 -0
  17. package/carbon/LICENSE +21 -0
  18. package/carbon/PRODUCTION.md +364 -0
  19. package/carbon/README.md +193 -0
  20. package/carbon/TESTING.md +138 -0
  21. package/carbon/backup/Dockerfile +75 -0
  22. package/carbon/backup/backup.sh +95 -0
  23. package/carbon/backup/compose-backup.sh +140 -0
  24. package/carbon/biome.json +83 -0
  25. package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
  26. package/carbon/cloud-init/k3s/master-init.sh +215 -0
  27. package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
  28. package/carbon/cloud-init/k3s/worker-init.sh +147 -0
  29. package/carbon/components.json +24 -0
  30. package/carbon/content/blog/authentication-guide.mdx +34 -0
  31. package/carbon/content/blog/getting-started.mdx +41 -0
  32. package/carbon/content/changelog/v0-1-0.mdx +40 -0
  33. package/carbon/content/docs/analytics.mdx +90 -0
  34. package/carbon/content/docs/authentication.mdx +231 -0
  35. package/carbon/content/docs/background-jobs.mdx +116 -0
  36. package/carbon/content/docs/cli.mdx +630 -0
  37. package/carbon/content/docs/database.mdx +236 -0
  38. package/carbon/content/docs/deployment.mdx +227 -0
  39. package/carbon/content/docs/development.mdx +238 -0
  40. package/carbon/content/docs/environments.mdx +84 -0
  41. package/carbon/content/docs/getting-started.mdx +112 -0
  42. package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
  43. package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
  44. package/carbon/content/docs/optional-services.mdx +160 -0
  45. package/carbon/db/Dockerfile +23 -0
  46. package/carbon/docker-compose.dev-init.yml +5 -0
  47. package/carbon/docker-compose.metabase.override.yml +14 -0
  48. package/carbon/docker-compose.metabase.prod.yml +28 -0
  49. package/carbon/docker-compose.metabase.yml +84 -0
  50. package/carbon/docker-compose.n8n.override.yml +14 -0
  51. package/carbon/docker-compose.n8n.prod.yml +31 -0
  52. package/carbon/docker-compose.n8n.yml +97 -0
  53. package/carbon/docker-compose.observability.override.yml +18 -0
  54. package/carbon/docker-compose.observability.prod.yml +28 -0
  55. package/carbon/docker-compose.observability.yml +125 -0
  56. package/carbon/docker-compose.override.yml +28 -0
  57. package/carbon/docker-compose.prod.yml +294 -0
  58. package/carbon/docker-compose.yml +508 -0
  59. package/carbon/docker-entrypoint.sh +12 -0
  60. package/carbon/ha/activate-standby.sh +52 -0
  61. package/carbon/ha/primary-init.sql +36 -0
  62. package/carbon/ha/standby-init.sh +74 -0
  63. package/carbon/k8s/LICENSE +99 -0
  64. package/carbon/k8s/README.md +272 -0
  65. package/carbon/k8s/base/app/deployment.yaml +130 -0
  66. package/carbon/k8s/base/app/hpa.yaml +44 -0
  67. package/carbon/k8s/base/app/kustomization.yaml +10 -0
  68. package/carbon/k8s/base/app/network-policy.yaml +124 -0
  69. package/carbon/k8s/base/app/pdb.yaml +14 -0
  70. package/carbon/k8s/base/app/rbac.yaml +36 -0
  71. package/carbon/k8s/base/app/service.yaml +16 -0
  72. package/carbon/k8s/base/backup/cronjob.yaml +120 -0
  73. package/carbon/k8s/base/backup/kustomization.yaml +7 -0
  74. package/carbon/k8s/base/backup/network-policy.yaml +31 -0
  75. package/carbon/k8s/base/backup/rbac.yaml +7 -0
  76. package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
  77. package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
  78. package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
  79. package/carbon/k8s/base/config/configmap.yaml +48 -0
  80. package/carbon/k8s/base/config/kustomization.yaml +8 -0
  81. package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
  82. package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
  83. package/carbon/k8s/base/kustomization.yaml +43 -0
  84. package/carbon/k8s/base/namespace.yaml +7 -0
  85. package/carbon/k8s/base/network-policies.yaml +95 -0
  86. package/carbon/k8s/base/registry/kustomization.yaml +5 -0
  87. package/carbon/k8s/base/registry/local-registry.yaml +193 -0
  88. package/carbon/k8s/base/traefik/certificate.yaml +21 -0
  89. package/carbon/k8s/base/traefik/configmap.yaml +13 -0
  90. package/carbon/k8s/base/traefik/deployment.yaml +165 -0
  91. package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
  92. package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
  93. package/carbon/k8s/base/traefik/middleware.yaml +109 -0
  94. package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
  95. package/carbon/k8s/base/traefik/service.yaml +38 -0
  96. package/carbon/k8s/flux/README.md +49 -0
  97. package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
  98. package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
  99. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
  100. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
  101. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
  102. package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
  103. package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
  104. package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
  105. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
  106. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
  107. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
  108. package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
  109. package/carbon/k8s/infra/kustomization.yaml +21 -0
  110. package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
  111. package/carbon/k8s/overlays/local/configmap.yaml +23 -0
  112. package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
  113. package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
  114. package/carbon/k8s/overlays/local/namespace.yaml +8 -0
  115. package/carbon/k8s/overlays/local/secrets.yaml +32 -0
  116. package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
  117. package/carbon/k8s/test-local.sh +318 -0
  118. package/carbon/k8s/values/supabase.values.yaml +133 -0
  119. package/carbon/package.json +154 -0
  120. package/carbon/runtime.Dockerfile +17 -0
  121. package/carbon/scripts/_dev-jwt.mjs +45 -0
  122. package/carbon/scripts/check-rls.ts +53 -0
  123. package/carbon/scripts/dev-init.js +191 -0
  124. package/carbon/scripts/dev.js +191 -0
  125. package/carbon/scripts/docker-down.js +63 -0
  126. package/carbon/scripts/docker-logs.js +59 -0
  127. package/carbon/scripts/docker-up.js +222 -0
  128. package/carbon/scripts/generate-dev-configs.sh +131 -0
  129. package/carbon/scripts/generate-rss.ts +116 -0
  130. package/carbon/scripts/generate-sitemap.ts +102 -0
  131. package/carbon/scripts/k8s-apply.js +71 -0
  132. package/carbon/scripts/k8s-delete.js +75 -0
  133. package/carbon/scripts/lib/manifest.js +176 -0
  134. package/carbon/scripts/secret-scan.mjs +278 -0
  135. package/carbon/scripts/validate-dev-configs.sh +101 -0
  136. package/carbon/src/client/App.tsx +202 -0
  137. package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
  138. package/carbon/src/client/assets/hyperformant-light.svg +29 -0
  139. package/carbon/src/client/assets/logos/aider.svg +1 -0
  140. package/carbon/src/client/assets/logos/antigravity.svg +1 -0
  141. package/carbon/src/client/assets/logos/bolt.svg +1 -0
  142. package/carbon/src/client/assets/logos/claude-code.svg +1 -0
  143. package/carbon/src/client/assets/logos/copilot.svg +1 -0
  144. package/carbon/src/client/assets/logos/cursor.svg +1 -0
  145. package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
  146. package/carbon/src/client/assets/logos/lovable.svg +1 -0
  147. package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
  148. package/carbon/src/client/assets/logos/v0.svg +1 -0
  149. package/carbon/src/client/assets/logos/windsurf.svg +1 -0
  150. package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
  151. package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
  152. package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
  153. package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
  154. package/carbon/src/client/components/AppSidebar.tsx +760 -0
  155. package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
  156. package/carbon/src/client/components/CTAFooter.tsx +59 -0
  157. package/carbon/src/client/components/ComparisonSection.tsx +132 -0
  158. package/carbon/src/client/components/ContentPanel.tsx +66 -0
  159. package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
  160. package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
  161. package/carbon/src/client/components/FAQSection.tsx +76 -0
  162. package/carbon/src/client/components/FileUpload.tsx +210 -0
  163. package/carbon/src/client/components/HeaderActions.tsx +17 -0
  164. package/carbon/src/client/components/Hero.tsx +608 -0
  165. package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
  166. package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
  167. package/carbon/src/client/components/Logo.tsx +87 -0
  168. package/carbon/src/client/components/LogoStrip.tsx +76 -0
  169. package/carbon/src/client/components/MetricsStrip.tsx +48 -0
  170. package/carbon/src/client/components/Nav.tsx +195 -0
  171. package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
  172. package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
  173. package/carbon/src/client/components/PageHeader.tsx +24 -0
  174. package/carbon/src/client/components/PlanGate.tsx +36 -0
  175. package/carbon/src/client/components/PricingSection.tsx +371 -0
  176. package/carbon/src/client/components/SEO.tsx +34 -0
  177. package/carbon/src/client/components/SmoothScroll.tsx +45 -0
  178. package/carbon/src/client/components/TechStackSection.tsx +165 -0
  179. package/carbon/src/client/components/WorkflowSection.tsx +604 -0
  180. package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
  181. package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
  182. package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
  183. package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
  184. package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
  185. package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
  186. package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
  187. package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
  188. package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
  189. package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
  190. package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
  191. package/carbon/src/client/components/effects/index.ts +2 -0
  192. package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
  193. package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
  194. package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
  195. package/carbon/src/client/components/scroll/index.ts +2 -0
  196. package/carbon/src/client/components/ui/accordion.tsx +71 -0
  197. package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
  198. package/carbon/src/client/components/ui/alert.tsx +73 -0
  199. package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
  200. package/carbon/src/client/components/ui/avatar.tsx +91 -0
  201. package/carbon/src/client/components/ui/badge.tsx +50 -0
  202. package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
  203. package/carbon/src/client/components/ui/button-group.tsx +78 -0
  204. package/carbon/src/client/components/ui/button.tsx +120 -0
  205. package/carbon/src/client/components/ui/calendar.tsx +182 -0
  206. package/carbon/src/client/components/ui/card.tsx +85 -0
  207. package/carbon/src/client/components/ui/carousel.tsx +227 -0
  208. package/carbon/src/client/components/ui/chart.tsx +357 -0
  209. package/carbon/src/client/components/ui/checkbox.tsx +27 -0
  210. package/carbon/src/client/components/ui/collapsible.tsx +15 -0
  211. package/carbon/src/client/components/ui/command.tsx +178 -0
  212. package/carbon/src/client/components/ui/context-menu.tsx +233 -0
  213. package/carbon/src/client/components/ui/dialog.tsx +132 -0
  214. package/carbon/src/client/components/ui/drawer.tsx +118 -0
  215. package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
  216. package/carbon/src/client/components/ui/empty.tsx +94 -0
  217. package/carbon/src/client/components/ui/field.tsx +226 -0
  218. package/carbon/src/client/components/ui/hover-card.tsx +44 -0
  219. package/carbon/src/client/components/ui/input-group.tsx +146 -0
  220. package/carbon/src/client/components/ui/input-otp.tsx +83 -0
  221. package/carbon/src/client/components/ui/input.tsx +20 -0
  222. package/carbon/src/client/components/ui/item.tsx +188 -0
  223. package/carbon/src/client/components/ui/kbd.tsx +26 -0
  224. package/carbon/src/client/components/ui/label.tsx +21 -0
  225. package/carbon/src/client/components/ui/menubar.tsx +250 -0
  226. package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
  227. package/carbon/src/client/components/ui/pagination.tsx +102 -0
  228. package/carbon/src/client/components/ui/popover.tsx +75 -0
  229. package/carbon/src/client/components/ui/progress.tsx +66 -0
  230. package/carbon/src/client/components/ui/radio-group.tsx +36 -0
  231. package/carbon/src/client/components/ui/resizable.tsx +46 -0
  232. package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
  233. package/carbon/src/client/components/ui/select.tsx +186 -0
  234. package/carbon/src/client/components/ui/separator.tsx +21 -0
  235. package/carbon/src/client/components/ui/sheet.tsx +124 -0
  236. package/carbon/src/client/components/ui/sidebar.tsx +702 -0
  237. package/carbon/src/client/components/ui/skeleton.tsx +13 -0
  238. package/carbon/src/client/components/ui/slider.tsx +57 -0
  239. package/carbon/src/client/components/ui/sonner.tsx +45 -0
  240. package/carbon/src/client/components/ui/spinner.tsx +15 -0
  241. package/carbon/src/client/components/ui/switch.tsx +30 -0
  242. package/carbon/src/client/components/ui/table.tsx +89 -0
  243. package/carbon/src/client/components/ui/tabs.tsx +73 -0
  244. package/carbon/src/client/components/ui/textarea.tsx +18 -0
  245. package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
  246. package/carbon/src/client/components/ui/toggle.tsx +44 -0
  247. package/carbon/src/client/components/ui/tooltip.tsx +56 -0
  248. package/carbon/src/client/hooks/api/index.ts +14 -0
  249. package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
  250. package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
  251. package/carbon/src/client/hooks/use-mobile.ts +21 -0
  252. package/carbon/src/client/hooks/useMousePosition.ts +91 -0
  253. package/carbon/src/client/hooks/useNotifications.tsx +124 -0
  254. package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
  255. package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
  256. package/carbon/src/client/hooks/usePackageManager.ts +69 -0
  257. package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
  258. package/carbon/src/client/hooks/useRunningServices.ts +114 -0
  259. package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
  260. package/carbon/src/client/index.css +467 -0
  261. package/carbon/src/client/index.html +56 -0
  262. package/carbon/src/client/lib/admin-services.ts +151 -0
  263. package/carbon/src/client/lib/api.ts +32 -0
  264. package/carbon/src/client/lib/blog.ts +35 -0
  265. package/carbon/src/client/lib/changelog.ts +33 -0
  266. package/carbon/src/client/lib/docs-search.ts +101 -0
  267. package/carbon/src/client/lib/docs.ts +37 -0
  268. package/carbon/src/client/lib/i18n.ts +32 -0
  269. package/carbon/src/client/lib/supabase.ts +72 -0
  270. package/carbon/src/client/lib/tailwind-colors.ts +357 -0
  271. package/carbon/src/client/lib/theme.ts +117 -0
  272. package/carbon/src/client/lib/utils.ts +22 -0
  273. package/carbon/src/client/locales/de.json +529 -0
  274. package/carbon/src/client/locales/en.json +461 -0
  275. package/carbon/src/client/locales/es.json +529 -0
  276. package/carbon/src/client/locales/fr.json +529 -0
  277. package/carbon/src/client/locales/pt.json +529 -0
  278. package/carbon/src/client/main.tsx +56 -0
  279. package/carbon/src/client/mdx.d.ts +13 -0
  280. package/carbon/src/client/pages/ApiDocs.tsx +76 -0
  281. package/carbon/src/client/pages/AuthCallback.tsx +34 -0
  282. package/carbon/src/client/pages/Blog.tsx +167 -0
  283. package/carbon/src/client/pages/Changelog.tsx +171 -0
  284. package/carbon/src/client/pages/Charts.tsx +388 -0
  285. package/carbon/src/client/pages/Checkout.tsx +227 -0
  286. package/carbon/src/client/pages/Contact.tsx +174 -0
  287. package/carbon/src/client/pages/Dashboard.tsx +368 -0
  288. package/carbon/src/client/pages/Docs.tsx +372 -0
  289. package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
  290. package/carbon/src/client/pages/Home.tsx +187 -0
  291. package/carbon/src/client/pages/Legal.tsx +100 -0
  292. package/carbon/src/client/pages/Login.tsx +408 -0
  293. package/carbon/src/client/pages/MFAVerify.tsx +156 -0
  294. package/carbon/src/client/pages/NotFound.tsx +21 -0
  295. package/carbon/src/client/pages/Onboarding.tsx +246 -0
  296. package/carbon/src/client/pages/ResetPassword.tsx +200 -0
  297. package/carbon/src/client/pages/UIComponents.tsx +390 -0
  298. package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
  299. package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
  300. package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
  301. package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
  302. package/carbon/src/client/pages/admin/Logs.tsx +18 -0
  303. package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
  304. package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
  305. package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
  306. package/carbon/src/client/pages/admin/Settings.tsx +314 -0
  307. package/carbon/src/client/pages/admin/Theme.tsx +465 -0
  308. package/carbon/src/client/pages/admin/Users.tsx +365 -0
  309. package/carbon/src/client/pages/api-docs.css +156 -0
  310. package/carbon/src/client/pages/organizations/Details.tsx +200 -0
  311. package/carbon/src/client/pages/organizations/Members.tsx +402 -0
  312. package/carbon/src/client/pages/settings/Billing.tsx +473 -0
  313. package/carbon/src/client/pages/settings/Profile.tsx +160 -0
  314. package/carbon/src/client/pages/settings/Security.tsx +341 -0
  315. package/carbon/src/client/public/favicon.svg +19 -0
  316. package/carbon/src/client/public/robots.txt +8 -0
  317. package/carbon/src/server/billing/index.ts +104 -0
  318. package/carbon/src/server/billing/provider.ts +81 -0
  319. package/carbon/src/server/billing/providers/paddle.ts +314 -0
  320. package/carbon/src/server/billing/providers/polar.ts +325 -0
  321. package/carbon/src/server/billing/providers/stripe.ts +233 -0
  322. package/carbon/src/server/emails/templates.ts +116 -0
  323. package/carbon/src/server/index.ts +554 -0
  324. package/carbon/src/server/lib/auth.ts +6 -0
  325. package/carbon/src/server/lib/email.ts +64 -0
  326. package/carbon/src/server/lib/env.ts +112 -0
  327. package/carbon/src/server/lib/errors.ts +21 -0
  328. package/carbon/src/server/lib/logger.ts +17 -0
  329. package/carbon/src/server/lib/rate-limiter.ts +288 -0
  330. package/carbon/src/server/lib/request.ts +34 -0
  331. package/carbon/src/server/lib/stripe.ts +42 -0
  332. package/carbon/src/server/lib/supabase.ts +65 -0
  333. package/carbon/src/server/middleware/requirePlan.ts +80 -0
  334. package/carbon/src/server/routes/_internal/services-status.ts +958 -0
  335. package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
  336. package/carbon/src/server/routes/health.ts +48 -0
  337. package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
  338. package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
  339. package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
  340. package/carbon/src/server/routes/v1/auth.ts +390 -0
  341. package/carbon/src/server/routes/v1/billing.ts +718 -0
  342. package/carbon/src/server/routes/v1/contact.ts +93 -0
  343. package/carbon/src/server/routes/v1/index.ts +1333 -0
  344. package/carbon/src/server/routes/v1/newsletter.ts +181 -0
  345. package/carbon/src/server/routes/v1/performance.ts +157 -0
  346. package/carbon/src/server/routes/v1/stats.ts +170 -0
  347. package/carbon/src/server/routes/v1/theme.ts +106 -0
  348. package/carbon/src/server/routes/webhooks/billing.ts +376 -0
  349. package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
  350. package/carbon/src/server/types.ts +11 -0
  351. package/carbon/src/shared/pricing.ts +155 -0
  352. package/carbon/src/shared/types.ts +338 -0
  353. package/carbon/supabase/migrations/00001_init.sql +717 -0
  354. package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
  355. package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
  356. package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
  357. package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
  358. package/carbon/supabase/seed.sql +16 -0
  359. package/carbon/tests/_helpers/app.ts +45 -0
  360. package/carbon/tests/_helpers/env.ts +37 -0
  361. package/carbon/tests/_helpers/factories.ts +69 -0
  362. package/carbon/tests/_helpers/jwt.ts +23 -0
  363. package/carbon/tests/_helpers/setup-integration.ts +20 -0
  364. package/carbon/tests/_helpers/setup-rtl.ts +12 -0
  365. package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
  366. package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
  367. package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
  368. package/carbon/tests/integration/server/routes/health.test.ts +61 -0
  369. package/carbon/tests/structural/i18n-parity.test.ts +42 -0
  370. package/carbon/tests/unit/client/utils.test.ts +49 -0
  371. package/carbon/tests/unit/shared/pricing.test.ts +93 -0
  372. package/carbon/tsconfig.json +27 -0
  373. package/carbon/tsconfig.server.json +27 -0
  374. package/carbon/tsconfig.test.json +9 -0
  375. package/carbon/vite.config.ts +110 -0
  376. package/carbon/vitest.config.ts +74 -0
  377. package/carbon/volumes/db/jwt.sql +57 -0
  378. package/carbon/volumes/db/metabase-init.sh +29 -0
  379. package/carbon/volumes/db/n8n-init.sh +25 -0
  380. package/carbon/volumes/db/realtime.sql +33 -0
  381. package/carbon/volumes/db/roles.sql +93 -0
  382. package/carbon/volumes/db/set-passwords.sh +12 -0
  383. package/carbon/volumes/db/super-admin.dev.sql +113 -0
  384. package/carbon/volumes/db/super-admin.generated.sql +113 -0
  385. package/carbon/volumes/db/super-admin.sql +114 -0
  386. package/carbon/volumes/grafana/dashboards/logs.json +179 -0
  387. package/carbon/volumes/grafana/dashboards/overview.json +523 -0
  388. package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
  389. package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
  390. package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
  391. package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
  392. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
  393. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
  394. package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
  395. package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
  396. package/carbon/volumes/kong/kong.yml +208 -0
  397. package/carbon/volumes/loki/loki-config.yml +58 -0
  398. package/carbon/volumes/n8n/hooks.js +131 -0
  399. package/carbon/volumes/n8n/scripts/setup.sh +66 -0
  400. package/carbon/volumes/prometheus/prometheus.yml +43 -0
  401. package/carbon/volumes/promtail/promtail-config.yml +64 -0
  402. package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
  403. package/carbon/volumes/traefik/middlewares.yml +95 -0
  404. package/carbon/volumes/traefik/vite-dev.yml +20 -0
  405. package/package.json +95 -0
  406. package/src/access.js +354 -0
  407. package/src/activate.js +187 -0
  408. package/src/add.js +718 -0
  409. package/src/backup.js +786 -0
  410. package/src/cli.js +350 -0
  411. package/src/configure.js +967 -0
  412. package/src/console.js +155 -0
  413. package/src/create.js +1499 -0
  414. package/src/deploy.js +311 -0
  415. package/src/destroy.js +2033 -0
  416. package/src/diagnose.js +735 -0
  417. package/src/down.js +80 -0
  418. package/src/failover.js +1032 -0
  419. package/src/lib/backup-s3.js +179 -0
  420. package/src/lib/build.js +33 -0
  421. package/src/lib/checksum.js +28 -0
  422. package/src/lib/ci-setup.js +666 -0
  423. package/src/lib/cli/help.js +129 -0
  424. package/src/lib/cli/parse-flags.js +160 -0
  425. package/src/lib/cli/select-action.js +65 -0
  426. package/src/lib/cli/select-environment.js +108 -0
  427. package/src/lib/cli/tty-guard.js +75 -0
  428. package/src/lib/cloudflare.js +447 -0
  429. package/src/lib/colors.js +52 -0
  430. package/src/lib/command.js +361 -0
  431. package/src/lib/config.js +359 -0
  432. package/src/lib/cost.js +103 -0
  433. package/src/lib/deploy/bundle.js +231 -0
  434. package/src/lib/deploy/compose/acme-verify.js +121 -0
  435. package/src/lib/deploy/compose/build-args.js +78 -0
  436. package/src/lib/deploy/compose/ha.js +1874 -0
  437. package/src/lib/deploy/compose/index.js +1294 -0
  438. package/src/lib/deploy/compose/reconcile.js +74 -0
  439. package/src/lib/deploy/github.js +382 -0
  440. package/src/lib/deploy/image.js +191 -0
  441. package/src/lib/deploy/index.js +119 -0
  442. package/src/lib/deploy/k8s/LICENSE +99 -0
  443. package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
  444. package/src/lib/deploy/k8s/ha/index.js +1000 -0
  445. package/src/lib/deploy/k8s/index.js +62 -0
  446. package/src/lib/deploy/k8s/k3s.js +2515 -0
  447. package/src/lib/deploy/orchestrator.js +1401 -0
  448. package/src/lib/deploy/prompts.js +545 -0
  449. package/src/lib/deploy/remote-build.js +130 -0
  450. package/src/lib/deploy/state.js +120 -0
  451. package/src/lib/deploy/utils.js +328 -0
  452. package/src/lib/deploy-logger.js +93 -0
  453. package/src/lib/dns-propagation.js +48 -0
  454. package/src/lib/environment.js +58 -0
  455. package/src/lib/fetch-retry.js +103 -0
  456. package/src/lib/github-environments.js +335 -0
  457. package/src/lib/hetzner-dns.js +377 -0
  458. package/src/lib/hetzner-guided-setup.js +275 -0
  459. package/src/lib/host-keys.js +37 -0
  460. package/src/lib/iac/cloud-init.js +52 -0
  461. package/src/lib/iac/index.js +325 -0
  462. package/src/lib/iac/programs/hetzner-compose.js +130 -0
  463. package/src/lib/iac/programs/hetzner-k8s.js +320 -0
  464. package/src/lib/kubectl.js +81 -0
  465. package/src/lib/licensing/index.js +259 -0
  466. package/src/lib/licensing/tiers.js +181 -0
  467. package/src/lib/licensing/validator.js +171 -0
  468. package/src/lib/merge-package-json.js +90 -0
  469. package/src/lib/operator-ip.js +381 -0
  470. package/src/lib/package-manager.js +111 -0
  471. package/src/lib/perf.js +71 -0
  472. package/src/lib/project-guard.js +39 -0
  473. package/src/lib/project.js +334 -0
  474. package/src/lib/providers/base.js +276 -0
  475. package/src/lib/providers/hetzner-s3.js +656 -0
  476. package/src/lib/providers/hetzner.js +755 -0
  477. package/src/lib/providers/index.js +164 -0
  478. package/src/lib/s3.js +510 -0
  479. package/src/lib/secret-scan.js +583 -0
  480. package/src/lib/secrets.js +63 -0
  481. package/src/lib/server-types.js +195 -0
  482. package/src/lib/shell.js +91 -0
  483. package/src/lib/ssh.js +241 -0
  484. package/src/lib/tracker.js +242 -0
  485. package/src/lib/upgrade-policy.js +170 -0
  486. package/src/lib/validators.js +105 -0
  487. package/src/lib/version.js +5 -0
  488. package/src/remove.js +292 -0
  489. package/src/reset.js +97 -0
  490. package/src/restore.js +871 -0
  491. package/src/scale.js +1734 -0
  492. package/src/shell.js +222 -0
  493. package/src/status.js +981 -0
  494. package/src/up.js +264 -0
  495. package/src/upgrade.js +721 -0
package/src/access.js ADDED
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Vibecarbon Access Command
3
+ *
4
+ * Manage the project-level operator-CIDR allowlist that locks SSH (port 22)
5
+ * and the Kubernetes API (port 6443) on all deployed environments' Hetzner
6
+ * firewalls. Most operators won't run this directly — `vibecarbon deploy`,
7
+ * `vibecarbon shell`, etc. auto-add the current operator's IP on first use
8
+ * (interactive flows only). This command exists for explicit management:
9
+ * adding a teammate's CIDR, pruning stale entries, or showing the list.
10
+ *
11
+ * Usage:
12
+ * vibecarbon access # List current CIDRs (default)
13
+ * vibecarbon access list # Same as above (explicit)
14
+ * vibecarbon access add <cidr> # Add + push to all firewalls
15
+ * vibecarbon access remove <cidr> # Remove + push to all firewalls
16
+ * vibecarbon access prune # Drop entries older than 90d, push
17
+ * vibecarbon access -h
18
+ */
19
+
20
+ import * as p from '@clack/prompts';
21
+ import { renderHelp } from './lib/cli/help.js';
22
+ import { parseFlags } from './lib/cli/parse-flags.js';
23
+ import { c, printBanner } from './lib/colors.js';
24
+ import { loadCredentials, saveProjectConfig } from './lib/config.js';
25
+ import {
26
+ addCidr,
27
+ applyToFirewall,
28
+ cidrFromIp,
29
+ detectOperatorIp,
30
+ loadOperatorCidrs,
31
+ pruneCidrs,
32
+ removeCidr,
33
+ } from './lib/operator-ip.js';
34
+ import { assertInProjectDir } from './lib/project-guard.js';
35
+ import { VERSION } from './lib/version.js';
36
+
37
+ const VALID_SUBCOMMANDS = new Set(['list', 'add', 'remove', 'prune']);
38
+
39
+ /** @type {import('./lib/cli/parse-flags.js').CommandSpec & { summary?: string, description?: string, examples?: Array<{ command: string, description?: string }> }} */
40
+ const SPEC = {
41
+ name: 'access',
42
+ summary: 'Manage operator-CIDR firewall allowlist',
43
+ description: [
44
+ "Most operators don't need to run this. `vibecarbon deploy` and",
45
+ '`vibecarbon shell` (and other commands that need port 22 or 6443)',
46
+ "auto-add the current operator's IP on first interactive use.",
47
+ '',
48
+ 'Use `add` to grant a teammate access, `remove` to revoke, `prune`',
49
+ "to clean up entries from operators who haven't connected in 90+ days.",
50
+ '',
51
+ 'SUBCOMMANDS',
52
+ ' list Show current allowed CIDRs (default)',
53
+ ' add <cidr> Add a CIDR to the allowlist + apply to firewalls',
54
+ ' remove <cidr> Remove a CIDR + apply to firewalls',
55
+ ' prune Drop entries with lastUsedAt > 90 days, apply',
56
+ ].join('\n'),
57
+ positional: [
58
+ { name: 'subcommand', optional: true, description: 'list | add | remove | prune' },
59
+ { name: 'cidr', optional: true, description: 'CIDR for add/remove (e.g. 1.2.3.4/32)' },
60
+ ],
61
+ flags: [
62
+ { name: 'h', boolean: true, description: 'Show this help' },
63
+ { name: 'v', boolean: true, description: 'Show version' },
64
+ ],
65
+ examples: [
66
+ { command: 'vibecarbon access' },
67
+ { command: 'vibecarbon access add 5.6.7.8/32' },
68
+ { command: 'vibecarbon access remove 5.6.7.8/32' },
69
+ { command: 'vibecarbon access prune' },
70
+ ],
71
+ };
72
+
73
+ // ============================================================================
74
+ // HELPERS
75
+ // ============================================================================
76
+
77
+ function isValidCidrFormat(cidr) {
78
+ if (typeof cidr !== 'string') return false;
79
+ const slashIdx = cidr.indexOf('/');
80
+ if (slashIdx < 0) return false;
81
+ const base = cidr.slice(0, slashIdx);
82
+ const bits = Number(cidr.slice(slashIdx + 1));
83
+ if (!Number.isInteger(bits) || bits < 0) return false;
84
+ const isV6 = base.includes(':');
85
+ if (isV6) return bits <= 128 && /^[0-9a-fA-F:]+$/.test(base);
86
+ // IPv4
87
+ if (bits > 32) return false;
88
+ if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(base)) return false;
89
+ return base.split('.').every((p) => {
90
+ const n = Number(p);
91
+ return Number.isInteger(n) && n >= 0 && n <= 255;
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Build the list of full env identifiers whose firewalls should be patched.
97
+ * For HA envs, include both -primary and -standby.
98
+ */
99
+ function deployedFirewallEnvs(projectConfig) {
100
+ const envs = [];
101
+ const environments = projectConfig?.environments ?? {};
102
+ for (const [name, cfg] of Object.entries(environments)) {
103
+ if (cfg?.ha?.enabled) {
104
+ envs.push(`${name}-primary`, `${name}-standby`);
105
+ } else {
106
+ envs.push(name);
107
+ }
108
+ }
109
+ return envs;
110
+ }
111
+
112
+ function formatEntry(entry, currentDetectedCidr, now) {
113
+ const labelOwn = currentDetectedCidr === entry.cidr ? c.bold('you') : c.dim('unknown');
114
+ const addedAt = formatRelative(entry.addedAt, now);
115
+ const usedAt = formatRelative(entry.lastUsedAt, now);
116
+ const usedDays = daysBetween(entry.lastUsedAt, now);
117
+ const stale = usedDays >= 90 ? c.warning(' (stale)') : '';
118
+ return ` ${entry.cidr.padEnd(20)} ${labelOwn.padEnd(20)} added ${addedAt}, used ${usedAt}${stale}`;
119
+ }
120
+
121
+ function daysBetween(isoDate, now) {
122
+ const t = new Date(isoDate).getTime();
123
+ if (!Number.isFinite(t)) return Number.POSITIVE_INFINITY;
124
+ return Math.floor((now.getTime() - t) / 86_400_000);
125
+ }
126
+
127
+ function formatRelative(isoDate, now) {
128
+ const days = daysBetween(isoDate, now);
129
+ if (!Number.isFinite(days)) return 'unknown';
130
+ if (days <= 0) return 'today';
131
+ if (days === 1) return 'yesterday';
132
+ if (days < 30) return `${days} days ago`;
133
+ if (days < 365) return `${Math.floor(days / 30)} months ago`;
134
+ return `${Math.floor(days / 365)} years ago`;
135
+ }
136
+
137
+ async function pushToAllFirewalls(projectConfig, list, apiToken, spinner) {
138
+ const envs = deployedFirewallEnvs(projectConfig);
139
+ if (envs.length === 0) {
140
+ spinner?.stop('No deployed environments — nothing to push to.');
141
+ return [];
142
+ }
143
+ if (list.length === 0) {
144
+ throw new Error(
145
+ 'Cannot push an empty CIDR list — that would lock everyone out of every environment.',
146
+ );
147
+ }
148
+ spinner?.start(`Updating firewall on ${envs.length} cluster(s)...`);
149
+ const updated = await applyToFirewall({
150
+ projectName: projectConfig.projectName,
151
+ environments: envs,
152
+ operatorCidrs: list,
153
+ apiToken,
154
+ });
155
+ spinner?.stop(`Updated ${updated.length} firewall(s).`);
156
+ return updated;
157
+ }
158
+
159
+ // ============================================================================
160
+ // SUBCOMMANDS
161
+ // ============================================================================
162
+
163
+ async function listAction(projectConfig) {
164
+ const list = loadOperatorCidrs(projectConfig);
165
+ if (list.length === 0) {
166
+ p.note(
167
+ 'No operator CIDRs configured yet. Run an interactive `vibecarbon deploy` or `vibecarbon shell` to bootstrap, or use `vibecarbon access add <cidr>`.',
168
+ 'Empty allowlist',
169
+ );
170
+ return;
171
+ }
172
+
173
+ let detectedCidr = null;
174
+ try {
175
+ const { ip, version } = await detectOperatorIp();
176
+ detectedCidr = cidrFromIp(ip, version);
177
+ } catch {
178
+ // detection unavailable — show list without `you` label
179
+ }
180
+
181
+ const now = new Date();
182
+ const lines = list.map((entry) => formatEntry(entry, detectedCidr, now));
183
+ p.note(lines.join('\n'), `Operator CIDRs (${list.length})`);
184
+ if (detectedCidr && !list.find((e) => e.cidr === detectedCidr)) {
185
+ p.log.info(
186
+ `Your detected IP (${detectedCidr}) is NOT in the list. ` +
187
+ `Run \`vibecarbon access add ${detectedCidr}\` to grant yourself access, ` +
188
+ `or run any command that needs SSH/k8s API to auto-add it.`,
189
+ );
190
+ }
191
+ }
192
+
193
+ async function addAction(projectConfig, cidr, apiToken) {
194
+ if (!cidr) throw new Error('Missing CIDR. Usage: vibecarbon access add <cidr>');
195
+ if (!isValidCidrFormat(cidr)) {
196
+ throw new Error(`Invalid CIDR format: ${cidr}. Expected e.g. "1.2.3.4/32" or "10.0.0.0/8".`);
197
+ }
198
+ const list = loadOperatorCidrs(projectConfig);
199
+ const existed = list.some((e) => e.cidr === cidr);
200
+ const newList = addCidr(list, cidr);
201
+
202
+ saveProjectConfig({ ...projectConfig, operatorCidrs: newList });
203
+ projectConfig.operatorCidrs = newList;
204
+
205
+ if (existed) {
206
+ p.log.info(`${cidr} was already in the list — refreshed lastUsedAt.`);
207
+ } else {
208
+ p.log.success(`Added ${cidr} to the operator allowlist.`);
209
+ }
210
+
211
+ const s = p.spinner();
212
+ await pushToAllFirewalls(projectConfig, newList, apiToken, s);
213
+ }
214
+
215
+ async function removeAction(projectConfig, cidr, apiToken) {
216
+ if (!cidr) throw new Error('Missing CIDR. Usage: vibecarbon access remove <cidr>');
217
+ const list = loadOperatorCidrs(projectConfig);
218
+ if (!list.some((e) => e.cidr === cidr)) {
219
+ p.log.warn(`${cidr} is not in the list — nothing to remove.`);
220
+ return;
221
+ }
222
+ if (list.length === 1) {
223
+ throw new Error(
224
+ `Refusing to remove the last CIDR (${cidr}) — that would lock everyone out of every environment. Add another entry first, or use \`vibecarbon destroy\` if you want to tear down.`,
225
+ );
226
+ }
227
+
228
+ // Self-lockout warning when removing the operator's own current CIDR.
229
+ try {
230
+ const { ip, version } = await detectOperatorIp();
231
+ const myCidr = cidrFromIp(ip, version);
232
+ if (myCidr === cidr) {
233
+ const ok = await p.confirm({
234
+ message: `${cidr} is your current IP. Removing it will lock you out until you reconnect from a different network or re-add it. Continue?`,
235
+ initialValue: false,
236
+ });
237
+ if (!ok || p.isCancel(ok)) {
238
+ p.cancel('Cancelled.');
239
+ return;
240
+ }
241
+ }
242
+ } catch {
243
+ // detection unavailable — proceed without the safety prompt
244
+ }
245
+
246
+ const newList = removeCidr(list, cidr);
247
+ saveProjectConfig({ ...projectConfig, operatorCidrs: newList });
248
+ projectConfig.operatorCidrs = newList;
249
+ p.log.success(`Removed ${cidr} from the operator allowlist.`);
250
+
251
+ const s = p.spinner();
252
+ await pushToAllFirewalls(projectConfig, newList, apiToken, s);
253
+ }
254
+
255
+ async function pruneAction(projectConfig, apiToken) {
256
+ const list = loadOperatorCidrs(projectConfig);
257
+ const now = new Date();
258
+ const newList = pruneCidrs(list, now, 90);
259
+ const dropped = list.length - newList.length;
260
+ if (dropped === 0) {
261
+ p.log.info('No stale entries to prune (none older than 90 days).');
262
+ return;
263
+ }
264
+ if (newList.length === 0) {
265
+ throw new Error(
266
+ `Refusing to prune all ${dropped} entries — that would lock everyone out. Run \`vibecarbon access add <cidr>\` first to seed at least one fresh entry, then re-run prune.`,
267
+ );
268
+ }
269
+ saveProjectConfig({ ...projectConfig, operatorCidrs: newList });
270
+ projectConfig.operatorCidrs = newList;
271
+ p.log.success(`Pruned ${dropped} stale entr${dropped === 1 ? 'y' : 'ies'}.`);
272
+
273
+ const s = p.spinner();
274
+ await pushToAllFirewalls(projectConfig, newList, apiToken, s);
275
+ }
276
+
277
+ // ============================================================================
278
+ // ENTRY POINT
279
+ // ============================================================================
280
+
281
+ export async function run(args = []) {
282
+ const { values, positional, errors } = parseFlags(args, SPEC);
283
+
284
+ if (errors.length > 0) {
285
+ for (const e of errors) {
286
+ process.stderr.write(`${c.error('✗')} ${e}\n`);
287
+ }
288
+ process.stderr.write(`Run ${c.info('vibecarbon access -h')} for usage.\n`);
289
+ process.exit(1);
290
+ }
291
+
292
+ if (values.h) {
293
+ process.stdout.write(renderHelp(SPEC));
294
+ return;
295
+ }
296
+ if (values.v) {
297
+ console.log(VERSION);
298
+ return;
299
+ }
300
+
301
+ // Resolve subcommand: if first positional matches a known verb, use it;
302
+ // otherwise default to `list` and treat the positional as a stray CIDR
303
+ // (which the strict subcommands reject below — list ignores it).
304
+ const sub = /** @type {string|undefined} */ (positional.subcommand);
305
+ let subcommand = 'list';
306
+ let cidr = /** @type {string|undefined} */ (positional.cidr) ?? null;
307
+ if (sub) {
308
+ if (VALID_SUBCOMMANDS.has(sub)) {
309
+ subcommand = sub;
310
+ } else {
311
+ // First positional wasn't a known subcommand — treat it as the CIDR
312
+ // for an implicit `list`, matching the previous parser's behavior.
313
+ cidr = sub;
314
+ }
315
+ }
316
+
317
+ // Project guard runs before banner so an accidental `vibecarbon
318
+ // access` from a parent directory emits the canonical message.
319
+ const projectConfig = assertInProjectDir();
320
+
321
+ printBanner();
322
+
323
+ const apiToken = process.env.HETZNER_API_TOKEN || loadCredentials()?.hetzner?.apiToken;
324
+ // The list action doesn't need a token; the others do for the firewall push.
325
+ if (subcommand !== 'list' && !apiToken) {
326
+ p.log.error(
327
+ 'Hetzner API token not found. Set HETZNER_API_TOKEN or populate ~/.vibecarbon/credentials.json.',
328
+ );
329
+ process.exit(1);
330
+ }
331
+
332
+ try {
333
+ switch (subcommand) {
334
+ case 'list':
335
+ await listAction(projectConfig);
336
+ break;
337
+ case 'add':
338
+ await addAction(projectConfig, cidr, apiToken);
339
+ break;
340
+ case 'remove':
341
+ await removeAction(projectConfig, cidr, apiToken);
342
+ break;
343
+ case 'prune':
344
+ await pruneAction(projectConfig, apiToken);
345
+ break;
346
+ default:
347
+ process.stdout.write(renderHelp(SPEC));
348
+ process.exit(1);
349
+ }
350
+ } catch (err) {
351
+ p.log.error(err.message || String(err));
352
+ process.exit(1);
353
+ }
354
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Vibecarbon Activate / Deactivate Commands
3
+ * Top-level commands for license management
4
+ *
5
+ * Usage:
6
+ * vibecarbon activate <key> # Activate a license key
7
+ * vibecarbon activate # Prompt for key interactively
8
+ * vibecarbon deactivate # Remove the current license
9
+ */
10
+
11
+ import * as p from '@clack/prompts';
12
+ import { renderHelp } from './lib/cli/help.js';
13
+ import { parseFlags } from './lib/cli/parse-flags.js';
14
+ import { c, printBanner } from './lib/colors.js';
15
+ import { activateLicense, deactivateLicense, getLicense } from './lib/licensing/index.js';
16
+ import { VERSION } from './lib/version.js';
17
+
18
+ /** @type {import('./lib/cli/parse-flags.js').CommandSpec & { summary?: string }} */
19
+ const ACTIVATE_SPEC = {
20
+ name: 'activate',
21
+ summary: 'Activate a Vibecarbon license key',
22
+ positional: [
23
+ {
24
+ name: 'key',
25
+ optional: true,
26
+ description: 'License key (vc-... or CARBON-...). Prompts if omitted.',
27
+ },
28
+ ],
29
+ flags: [{ name: 'h', boolean: true, description: 'Show this help' }],
30
+ };
31
+
32
+ /** @type {import('./lib/cli/parse-flags.js').CommandSpec & { summary?: string }} */
33
+ const DEACTIVATE_SPEC = {
34
+ name: 'deactivate',
35
+ summary: 'Deactivate the current Vibecarbon license',
36
+ flags: [
37
+ { name: 'h', boolean: true, description: 'Show this help' },
38
+ { name: 'y', boolean: true, description: 'Skip confirmation prompt' },
39
+ ],
40
+ };
41
+
42
+ /**
43
+ * Activate a license key (Diamond or Fullerene)
44
+ * @param {string[]} args - CLI arguments (first positional arg is the key)
45
+ */
46
+ export async function runActivate(args) {
47
+ const { values, positional, errors } = parseFlags(args, ACTIVATE_SPEC);
48
+
49
+ if (errors.length > 0) {
50
+ for (const e of errors) {
51
+ process.stderr.write(`${c.error('✗')} ${e}\n`);
52
+ }
53
+ process.stderr.write(`Run ${c.info('vibecarbon activate -h')} for usage.\n`);
54
+ process.exit(1);
55
+ }
56
+
57
+ if (values.h) {
58
+ process.stdout.write(renderHelp(ACTIVATE_SPEC));
59
+ return;
60
+ }
61
+
62
+ printBanner();
63
+ p.intro(`${c.bold('vibecarbon activate')} ${c.dim(`v${VERSION}`)}`);
64
+
65
+ // Check if already activated
66
+ const currentLicense = getLicense();
67
+ if (currentLicense.active) {
68
+ p.log.info(`You already have an active ${c.success(currentLicense.displayName)} license.`);
69
+ p.log.info(`Customer ID: ${c.dim(currentLicense.customerId)}`);
70
+ const proceed = await p.confirm({
71
+ message: 'Replace with a new license key?',
72
+ initialValue: false,
73
+ });
74
+ if (p.isCancel(proceed) || !proceed) {
75
+ p.outro('Keeping current license.');
76
+ return;
77
+ }
78
+ }
79
+
80
+ // Get key from args or prompt
81
+ let licenseKey = /** @type {string|undefined} */ (positional.key);
82
+
83
+ if (!licenseKey) {
84
+ const inputKey = await p.text({
85
+ message: 'Enter your license key:',
86
+ placeholder: 'vc-xxxxxxxx-signature...',
87
+ validate: (value) => {
88
+ if (!value) return 'License key is required';
89
+ const trimmed = value.trim().toLowerCase();
90
+ if (!trimmed.startsWith('vc-') && !trimmed.startsWith('carbon-')) {
91
+ return 'Invalid key format. Expected vc-... or CARBON-...';
92
+ }
93
+ return undefined;
94
+ },
95
+ });
96
+
97
+ if (p.isCancel(inputKey)) {
98
+ p.cancel('Operation cancelled.');
99
+ process.exit(0);
100
+ }
101
+
102
+ licenseKey = inputKey;
103
+ }
104
+
105
+ const spinner = p.spinner();
106
+ spinner.start('Validating license key...');
107
+
108
+ const result = activateLicense(licenseKey);
109
+
110
+ if (!result.success) {
111
+ spinner.stop('License validation failed');
112
+ p.log.error(c.error(`Error: ${result.error}`));
113
+ p.log.info('');
114
+ p.log.info(`${c.dim('Purchase a license at')} ${c.info('https://vibecarbon.dev/pricing')}`);
115
+ process.exit(1);
116
+ }
117
+
118
+ spinner.stop('License activated!');
119
+
120
+ p.log.success(`Welcome to ${c.success(result.tierName)}!`);
121
+
122
+ p.note(
123
+ `Tier: ${result.tierName}\nExpires: Never (lifetime license)\nFeatures: ${result.features.join(', ')}`,
124
+ 'License Details',
125
+ );
126
+
127
+ if (result.devMode) {
128
+ p.log.info(c.dim('[Development mode - signature verification skipped]'));
129
+ }
130
+
131
+ p.outro('You can now deploy, backup, scale, and operate your production stack.');
132
+ }
133
+
134
+ /**
135
+ * Deactivate the current license
136
+ * @param {string[]} args - CLI arguments
137
+ */
138
+ export async function runDeactivate(args) {
139
+ const { values, errors } = parseFlags(args, DEACTIVATE_SPEC);
140
+
141
+ if (errors.length > 0) {
142
+ for (const e of errors) {
143
+ process.stderr.write(`${c.error('✗')} ${e}\n`);
144
+ }
145
+ process.stderr.write(`Run ${c.info('vibecarbon deactivate -h')} for usage.\n`);
146
+ process.exit(1);
147
+ }
148
+
149
+ if (values.h) {
150
+ process.stdout.write(renderHelp(DEACTIVATE_SPEC));
151
+ return;
152
+ }
153
+
154
+ printBanner();
155
+ p.intro(`${c.bold('vibecarbon deactivate')} ${c.dim(`v${VERSION}`)}`);
156
+
157
+ const license = getLicense();
158
+
159
+ if (!license.active) {
160
+ p.log.info('No license is currently activated.');
161
+ p.outro('');
162
+ return;
163
+ }
164
+
165
+ const yes = !!values.y;
166
+
167
+ if (!yes) {
168
+ const confirm = await p.confirm({
169
+ message: `Deactivate your ${license.displayName} license? You will revert to the Graphite tier.`,
170
+ });
171
+
172
+ if (p.isCancel(confirm) || !confirm) {
173
+ p.cancel('Operation cancelled.');
174
+ process.exit(0);
175
+ }
176
+ }
177
+
178
+ const result = deactivateLicense();
179
+
180
+ if (!result.success) {
181
+ p.log.error(c.error(`Error: ${result.error}`));
182
+ process.exit(1);
183
+ }
184
+
185
+ p.log.success(c.success(result.message));
186
+ p.outro('You are now using the Graphite tier.');
187
+ }