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
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Shared help-text renderer for vibecarbon commands.
3
+ *
4
+ * Each command exports a `CommandSpec` (see `parse-flags.js` for the
5
+ * type) and this renderer produces the human help body from it. One
6
+ * authoritative source for usage info means flags can't drift between
7
+ * the parser and the help output the way they did when each command
8
+ * hand-rolled both — a common failure mode in the old code where a
9
+ * flag would be parsed but missing from `--help`, or vice-versa.
10
+ *
11
+ * The output style mirrors the existing tone: bold section headers,
12
+ * cyan flag/value names, dim descriptions. Sections are skipped
13
+ * silently when their data is empty (no flags? no FLAGS section).
14
+ *
15
+ * Vibecarbon is single-dash-only — flag names render as `-name`,
16
+ * never `--name`. See memory:feedback_cli_single_dash_flags.
17
+ */
18
+
19
+ import { c } from '../colors.js';
20
+
21
+ /**
22
+ * @typedef {object} HelpExample
23
+ * @property {string} command - the literal invocation (without
24
+ * the leading `$ ` shell prompt).
25
+ * @property {string} [description] - one-line context shown above
26
+ * the command in dim text.
27
+ *
28
+ * @typedef {import('./parse-flags.js').CommandSpec & {
29
+ * examples?: HelpExample[],
30
+ * description?: string,
31
+ * }} HelpSpec
32
+ */
33
+
34
+ /**
35
+ * Render a command's help body. Returns a string ending in a newline,
36
+ * suitable for `console.log()` or `process.stdout.write()`.
37
+ *
38
+ * @param {HelpSpec} spec
39
+ * @returns {string}
40
+ */
41
+ export function renderHelp(spec) {
42
+ const lines = [];
43
+
44
+ // Title line: "Vibecarbon Backup - Create or manage backups"
45
+ const title = `${c.bold('Vibecarbon')} ${c.bold(capitalize(spec.name))}`;
46
+ if (spec.summary) {
47
+ lines.push(`${title} - ${spec.summary}`);
48
+ } else {
49
+ lines.push(title);
50
+ }
51
+ lines.push('');
52
+
53
+ if (spec.description) {
54
+ lines.push(spec.description);
55
+ lines.push('');
56
+ }
57
+
58
+ // USAGE section.
59
+ lines.push(c.bold('USAGE'));
60
+ lines.push(` ${formatUsage(spec)}`);
61
+ lines.push('');
62
+
63
+ // ARGUMENTS section (only if there are positionals).
64
+ const positionals = spec.positional ?? [];
65
+ if (positionals.length > 0) {
66
+ lines.push(c.bold('ARGUMENTS'));
67
+ for (const p of positionals) {
68
+ const name = p.optional ? `[${p.name}]` : `<${p.name}>`;
69
+ const desc = p.description ?? '';
70
+ lines.push(` ${c.info(name.padEnd(16))} ${c.dim(desc)}`);
71
+ }
72
+ lines.push('');
73
+ }
74
+
75
+ // FLAGS section.
76
+ const flags = spec.flags ?? [];
77
+ if (flags.length > 0) {
78
+ lines.push(c.bold('FLAGS'));
79
+ for (const f of flags) {
80
+ const display = f.value ? `-${f.name} ${f.value}` : `-${f.name}`;
81
+ const desc = f.description ?? '';
82
+ const enumNote = f.enum ? `${desc ? ' ' : ''}(${f.enum.join('|')})` : '';
83
+ lines.push(` ${c.info(display.padEnd(20))} ${c.dim(desc + enumNote)}`);
84
+ }
85
+ lines.push('');
86
+ }
87
+
88
+ // EXAMPLES section.
89
+ const examples = spec.examples ?? [];
90
+ if (examples.length > 0) {
91
+ lines.push(c.bold('EXAMPLES'));
92
+ for (const ex of examples) {
93
+ if (ex.description) {
94
+ lines.push(` ${c.dim(`# ${ex.description}`)}`);
95
+ }
96
+ lines.push(` ${ex.command}`);
97
+ lines.push('');
98
+ }
99
+ }
100
+
101
+ return `${lines.join('\n').trimEnd()}\n`;
102
+ }
103
+
104
+ /**
105
+ * Build the one-line usage signature.
106
+ * @param {HelpSpec} spec
107
+ */
108
+ function formatUsage(spec) {
109
+ const parts = ['vibecarbon', spec.name];
110
+ for (const p of spec.positional ?? []) {
111
+ if (p.variadic) {
112
+ parts.push(p.optional ? `[${p.name}...]` : `<${p.name}...>`);
113
+ } else {
114
+ parts.push(p.optional ? `[${p.name}]` : `<${p.name}>`);
115
+ }
116
+ }
117
+ if ((spec.flags ?? []).length > 0) {
118
+ parts.push('[flags]');
119
+ }
120
+ return parts.join(' ');
121
+ }
122
+
123
+ /**
124
+ * @param {string} s
125
+ * @returns {string}
126
+ */
127
+ function capitalize(s) {
128
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
129
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Single-dash spec-driven flag parser.
3
+ *
4
+ * Vibecarbon's CLI uses single-dash flags only (`-h`, `-env prod`,
5
+ * `-mode k8s`). Double-dash long flags (`--help`) are rejected — see
6
+ * memory:feedback_cli_single_dash_flags. Each command exports a spec
7
+ * declaring its flags + positionals; this parser walks argv against
8
+ * that spec and returns `{ values, positional, errors }`.
9
+ *
10
+ * Why a shared parser:
11
+ * - Each command file used to hand-roll a `parseArgs(args)` loop.
12
+ * The result was inconsistent error handling, drift between long
13
+ * and short forms, and silent acceptance of typos.
14
+ * - With a shared parser driven by an explicit spec, every command
15
+ * gets the same diagnostics, and help-text generation has a single
16
+ * authoritative source (this spec, consumed by `cli/help.js`).
17
+ *
18
+ * @typedef {object} FlagSpec
19
+ * @property {string} name - canonical flag name (used as `-${name}` in argv).
20
+ * @property {boolean} [boolean] - true for presence-only flags (`-l`, `-y`).
21
+ * Boolean and value are mutually exclusive; pick one.
22
+ * @property {string} [value] - placeholder shown in help (e.g. `<name>`).
23
+ * When set, the parser consumes the next argv element as the value.
24
+ * @property {string[]} [enum] - constrain a value flag to an allowed set.
25
+ * The parser emits an error if the supplied value isn't in this list.
26
+ * @property {string} [description] - human-readable description for help.
27
+ *
28
+ * @typedef {object} PositionalSpec
29
+ * @property {string} name - identifier (used as the key in the result).
30
+ * @property {boolean} [optional=false] - if false, missing this positional
31
+ * produces an error.
32
+ * @property {boolean} [variadic=false] - if true, all remaining positionals
33
+ * are collected into an array under this name.
34
+ * @property {string} [description] - human-readable description for help.
35
+ *
36
+ * @typedef {object} CommandSpec
37
+ * @property {string} name - the command (e.g. `backup`).
38
+ * @property {string} [summary] - one-line description for help.
39
+ * @property {FlagSpec[]} [flags]
40
+ * @property {PositionalSpec[]} [positional]
41
+ *
42
+ * @typedef {object} ParseResult
43
+ * @property {Record<string, string|boolean|null>} values - flag values
44
+ * keyed by canonical name. Boolean flags default to false; value flags
45
+ * default to null.
46
+ * @property {Record<string, string|string[]|undefined>} positional -
47
+ * positional values keyed by their declared name. Variadic positionals
48
+ * produce an array.
49
+ * @property {string[]} errors - human-readable diagnostics. Empty when
50
+ * parsing succeeded; the caller decides whether to print and exit.
51
+ */
52
+
53
+ /**
54
+ * @param {string[]} argv - process.argv slice (without node + script).
55
+ * @param {CommandSpec} spec
56
+ * @returns {ParseResult}
57
+ */
58
+ export function parseFlags(argv, spec) {
59
+ /** @type {Record<string, string|boolean|null>} */
60
+ const values = {};
61
+ /** @type {Record<string, string|string[]|undefined>} */
62
+ const positional = {};
63
+ /** @type {string[]} */
64
+ const errors = [];
65
+
66
+ const flagSpecs = spec.flags ?? [];
67
+ const positionalSpecs = spec.positional ?? [];
68
+
69
+ for (const f of flagSpecs) {
70
+ if (f.boolean && f.value !== undefined) {
71
+ throw new Error(`flag spec "${f.name}" has both \`boolean\` and \`value\`; pick one`);
72
+ }
73
+ values[f.name] = f.boolean ? false : null;
74
+ }
75
+
76
+ /** @type {string[]} */
77
+ const collectedPositionals = [];
78
+
79
+ for (let i = 0; i < argv.length; i++) {
80
+ const arg = argv[i];
81
+
82
+ if (!arg.startsWith('-')) {
83
+ collectedPositionals.push(arg);
84
+ continue;
85
+ }
86
+
87
+ // Reject the empty `-` (probably a typo) and double-dash forms
88
+ // (`--help`, `--env=prod`). Both fall into the "unknown flag" path
89
+ // — vibecarbon is pre-release and intentionally has no migration
90
+ // shim for the dropped POSIX-long convention.
91
+ if (arg === '-' || arg.startsWith('--')) {
92
+ errors.push(`unknown flag: ${arg}`);
93
+ continue;
94
+ }
95
+
96
+ const name = arg.slice(1);
97
+ const flagSpec = flagSpecs.find((f) => f.name === name);
98
+
99
+ if (!flagSpec) {
100
+ errors.push(`unknown flag: ${arg}`);
101
+ continue;
102
+ }
103
+
104
+ if (flagSpec.boolean) {
105
+ values[name] = true;
106
+ continue;
107
+ }
108
+
109
+ // Value flag — consume the next argv element. Reject if the next
110
+ // element is missing or itself a flag (the latter usually means the
111
+ // operator forgot the value: `vibecarbon backup -env -y` is almost
112
+ // certainly a mistake, not "use literal `-y` as the env name").
113
+ const next = argv[i + 1];
114
+ if (next === undefined || next.startsWith('-')) {
115
+ errors.push(`flag -${name} requires a value`);
116
+ continue;
117
+ }
118
+ if (flagSpec.enum && !flagSpec.enum.includes(next)) {
119
+ errors.push(`-${name} must be one of: ${flagSpec.enum.join(', ')} (got: ${next})`);
120
+ // Still consume next so we don't double-report it as a stray positional.
121
+ i++;
122
+ continue;
123
+ }
124
+ values[name] = next;
125
+ i++;
126
+ }
127
+
128
+ // Distribute collected positionals across positional specs in order.
129
+ // A variadic spec consumes all remaining positionals, so it must be
130
+ // the last entry; we guard against mid-list variadics in the schema.
131
+ let cursor = 0;
132
+ for (let p = 0; p < positionalSpecs.length; p++) {
133
+ const ps = positionalSpecs[p];
134
+ if (ps.variadic) {
135
+ if (p !== positionalSpecs.length - 1) {
136
+ throw new Error(`positional "${ps.name}" is variadic but not the last entry in spec`);
137
+ }
138
+ positional[ps.name] = collectedPositionals.slice(cursor);
139
+ cursor = collectedPositionals.length;
140
+ break;
141
+ }
142
+ if (cursor < collectedPositionals.length) {
143
+ positional[ps.name] = collectedPositionals[cursor];
144
+ cursor++;
145
+ } else {
146
+ positional[ps.name] = undefined;
147
+ if (!ps.optional) {
148
+ errors.push(`missing required argument: ${ps.name}`);
149
+ }
150
+ }
151
+ }
152
+
153
+ // Excess positionals (operator passed more than the spec declares).
154
+ if (cursor < collectedPositionals.length) {
155
+ const extra = collectedPositionals.slice(cursor);
156
+ errors.push(`unexpected argument${extra.length === 1 ? '' : 's'}: ${extra.join(' ')}`);
157
+ }
158
+
159
+ return { values, positional, errors };
160
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Action-picker prompt for commands that branch on a verb.
3
+ *
4
+ * Some commands have multiple modes — `backup` can create, list, or
5
+ * download; `restore` can restore from S3 or from a local file. Under
6
+ * the new interactive-default model, these don't surface as flags;
7
+ * the bare command opens an action menu after the env is selected.
8
+ *
9
+ * The seed parameter (from `-action <verb>`) lets power users skip
10
+ * the prompt for repeatable invocations. Validation against the
11
+ * options list catches typos loudly instead of silently dropping
12
+ * into the prompt.
13
+ */
14
+
15
+ import * as p from '@clack/prompts';
16
+
17
+ /**
18
+ * @typedef {object} ActionOption
19
+ * @property {string} value - canonical identifier; what the seed
20
+ * matches against and what this helper returns.
21
+ * @property {string} label - what the operator sees in the picker.
22
+ * @property {string} [hint] - secondary text in the picker.
23
+ *
24
+ * @param {object} options
25
+ * @param {string} options.message - prompt text (e.g. "What do you
26
+ * want to do?").
27
+ * @param {ActionOption[]} options.choices
28
+ * @param {string|null} [options.seed] - operator-supplied verb (from
29
+ * the `-action` flag). When set, validated against choices and
30
+ * returned directly. When null/undefined, prompt.
31
+ * @returns {Promise<string>} the chosen `value`.
32
+ */
33
+ export async function selectAction(options) {
34
+ const { message, choices, seed } = options;
35
+
36
+ if (choices.length === 0) {
37
+ throw new Error('selectAction called with no choices — programming error');
38
+ }
39
+
40
+ if (seed) {
41
+ const known = choices.find((c) => c.value === seed);
42
+ if (!known) {
43
+ const valid = choices.map((c) => c.value).join(', ');
44
+ p.log.error(`Action '${seed}' is not valid. Choose one of: ${valid}.`);
45
+ process.exit(1);
46
+ }
47
+ return known.value;
48
+ }
49
+
50
+ if (choices.length === 1) {
51
+ return choices[0].value;
52
+ }
53
+
54
+ const choice = await p.select({
55
+ message,
56
+ options: choices,
57
+ });
58
+
59
+ if (p.isCancel(choice)) {
60
+ p.cancel('Operation cancelled.');
61
+ process.exit(0);
62
+ }
63
+
64
+ return /** @type {string} */ (choice);
65
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Environment-picker prompt for env-scoped commands.
3
+ *
4
+ * Replaces the older `lib/environment.js#resolveEnvironment` with a
5
+ * verb-aware variant suited to the new interactive-default CLI:
6
+ * - The prompt + empty-state error speak in terms of the operation
7
+ * ("back up", "deploy to", "destroy") so the message reads as
8
+ * guidance, not a generic env picker.
9
+ * - The picker hint includes the deploy status, server IP, and
10
+ * primary region so multi-env operators can pick by metadata
11
+ * instead of memorized name.
12
+ * - The seed (positional or flag) is passed in explicitly rather
13
+ * than read from a parsed-args struct, so callers using either
14
+ * the new `parse-flags.js` or hand-rolled argv can both consume
15
+ * it without an adapter.
16
+ *
17
+ * Returns `{ envName, envConfig }`. On empty state, missing seed
18
+ * with no TTY, cancel, or invalid seed, exits the process with a
19
+ * canonical message — same model the rest of the CLI uses for
20
+ * unrecoverable conditions.
21
+ *
22
+ * The old `resolveEnvironment` is retained so PR 1 doesn't change
23
+ * any command behavior. PR 2 migrates backup/restore to this helper
24
+ * and PR 6 deletes the old one.
25
+ */
26
+
27
+ import * as p from '@clack/prompts';
28
+ import { c } from '../colors.js';
29
+
30
+ /**
31
+ * @param {object} projectConfig - parsed `.vibecarbon.json`.
32
+ * @param {object} options
33
+ * @param {string} options.actionVerb - imperative phrase describing
34
+ * the operation, e.g. "back up", "deploy to", "restore", "destroy".
35
+ * Used in prompts and empty-state messaging.
36
+ * @param {string|null} [options.seed] - operator-supplied env name
37
+ * (from positional or flag). When set, skip the prompt and use it
38
+ * directly. When null/undefined, prompt (or use the single env if
39
+ * only one exists).
40
+ * @returns {Promise<{ envName: string, envConfig: object }>}
41
+ */
42
+ export async function selectEnvironment(projectConfig, options) {
43
+ const { actionVerb, seed } = options;
44
+ const environments = projectConfig.environments || {};
45
+ const envNames = Object.keys(environments);
46
+
47
+ if (envNames.length === 0) {
48
+ p.log.error(`No deployed environments to ${actionVerb}.`);
49
+ p.log.info(`Run ${c.info('vibecarbon deploy')} first.`);
50
+ process.exit(1);
51
+ }
52
+
53
+ // Seed from caller — validate against known envs, fail loudly if
54
+ // the operator typed a name that doesn't exist (so a typo doesn't
55
+ // silently fall through to a prompt that gets misinterpreted).
56
+ if (seed) {
57
+ if (!environments[seed]) {
58
+ p.log.error(`Environment '${seed}' not found.`);
59
+ p.log.info(`Available: ${envNames.join(', ')}`);
60
+ process.exit(1);
61
+ }
62
+ return { envName: seed, envConfig: environments[seed] };
63
+ }
64
+
65
+ // Single-env shortcut: no point prompting when there's only one
66
+ // answer. The operator might still want to see which env they're
67
+ // operating on; surface that as a log line so the prompt isn't
68
+ // silently bypassed.
69
+ if (envNames.length === 1) {
70
+ const only = envNames[0];
71
+ p.log.info(`Using environment: ${c.bold(only)} (only deployed env)`);
72
+ return { envName: only, envConfig: environments[only] };
73
+ }
74
+
75
+ // Multi-env: render a picker with metadata-rich hints.
76
+ const choice = await p.select({
77
+ message: `Which environment to ${actionVerb}?`,
78
+ options: envNames.map((name) => ({
79
+ value: name,
80
+ label: name,
81
+ hint: formatEnvHint(environments[name]),
82
+ })),
83
+ });
84
+
85
+ if (p.isCancel(choice)) {
86
+ p.cancel('Operation cancelled.');
87
+ process.exit(0);
88
+ }
89
+
90
+ return { envName: /** @type {string} */ (choice), envConfig: environments[choice] };
91
+ }
92
+
93
+ /**
94
+ * Build the hint string shown next to each env in the picker.
95
+ * Combines status + region + first server IP so operators can
96
+ * disambiguate by deploy state at a glance.
97
+ *
98
+ * @param {object} envConfig
99
+ * @returns {string}
100
+ */
101
+ function formatEnvHint(envConfig) {
102
+ const parts = [];
103
+ if (envConfig.status) parts.push(envConfig.status);
104
+ if (envConfig.region) parts.push(envConfig.region);
105
+ const ip = envConfig.servers?.[0]?.ip;
106
+ if (ip) parts.push(ip);
107
+ return parts.join(' · ');
108
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * TTY-detection guard for interactive-by-default commands.
3
+ *
4
+ * Vibecarbon's CLI is interactive-first: bare commands open a clack
5
+ * prompt flow. That model breaks when stdin isn't a TTY (CI, piped
6
+ * input, `< /dev/null` smoke tests). Rather than letting clack hang or
7
+ * silently misbehave, every command that intends to prompt calls
8
+ * `requireTTYOrFlags()` after parsing argv. If we're on a TTY, the call
9
+ * is a no-op. Off a TTY, the guard checks whether the operator supplied
10
+ * enough flags to skip every prompt the command would otherwise issue;
11
+ * if they did, fine — proceed. If they didn't, exit 1 with a message
12
+ * naming exactly which flags would unblock this invocation.
13
+ *
14
+ * Why this shape (and not "always require flags off-TTY"): some
15
+ * commands are no-prompt by nature (e.g. `vibecarbon backup -env prod
16
+ * -l` is a pure read). Demanding flags from those operators would be
17
+ * theatre. The guard takes a `requirements` array describing which
18
+ * prompts would otherwise run, and only complains about the ones the
19
+ * operator hasn't already short-circuited.
20
+ *
21
+ * @typedef {object} PromptRequirement
22
+ * @property {string} flag - the flag name that pre-fills this prompt
23
+ * (e.g. "env", "action").
24
+ * @property {string} description - what this prompt would ask, in
25
+ * imperative form (e.g. "select an environment").
26
+ * @property {boolean} satisfied - true if argv already supplies a
27
+ * value (operator pre-filled via flag or positional). When false,
28
+ * the command would have opened a prompt.
29
+ */
30
+
31
+ import { c } from '../colors.js';
32
+
33
+ /**
34
+ * Verify we're either on an interactive TTY OR the operator supplied
35
+ * every flag needed to skip the prompts. Exits 1 with a canonical
36
+ * "needs TTY or these flags" message otherwise. Returns void on
37
+ * success.
38
+ *
39
+ * @param {object} options
40
+ * @param {PromptRequirement[]} options.requirements - prompts the
41
+ * command would issue without flag short-circuits.
42
+ * @param {NodeJS.ReadStream} [options.stdin=process.stdin] - injectable
43
+ * for testing.
44
+ * @param {NodeJS.WriteStream} [options.stderr=process.stderr] -
45
+ * injectable for testing.
46
+ * @param {(code: number) => never} [options.exit=process.exit] -
47
+ * injectable for testing.
48
+ */
49
+ export function requireTTYOrFlags(options) {
50
+ const {
51
+ requirements,
52
+ stdin = process.stdin,
53
+ stderr = process.stderr,
54
+ exit = /** @type {(code: number) => never} */ (process.exit.bind(process)),
55
+ } = options;
56
+
57
+ if (stdin.isTTY) return;
58
+
59
+ const missing = requirements.filter((r) => !r.satisfied);
60
+ if (missing.length === 0) return;
61
+
62
+ // Format: one line per missing prompt with the flag that would
63
+ // unblock it, so the operator can copy-paste a working invocation.
64
+ const lines = [
65
+ '',
66
+ `${c.error('✗')} This command needs an interactive terminal, or these flags to skip prompts:`,
67
+ '',
68
+ ...missing.map((m) => ` -${m.flag.padEnd(10)} ${m.description}`),
69
+ '',
70
+ 'Run again with -h for the full flag reference.',
71
+ '',
72
+ ];
73
+ stderr.write(`${lines.join('\n')}\n`);
74
+ exit(1);
75
+ }