create-ailk 0.6.0 → 0.7.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 (248) hide show
  1. package/README.md +44 -5
  2. package/component-catalog.json +146 -21
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module-core.d.ts +22 -0
  5. package/dist/add-module-core.js +50 -0
  6. package/dist/add-module.d.ts +89 -0
  7. package/dist/add-module.js +334 -0
  8. package/dist/category-profile.d.ts +126 -0
  9. package/dist/category-profile.js +262 -0
  10. package/dist/cli.js +124 -13
  11. package/dist/copy.d.ts +14 -0
  12. package/dist/copy.js +38 -1
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +6 -1
  15. package/dist/lib/extract-module-bundle.d.ts +76 -2
  16. package/dist/lib/extract-module-bundle.js +219 -8
  17. package/dist/lib/license-key-files.d.ts +15 -0
  18. package/dist/lib/license-key-files.js +102 -0
  19. package/dist/lib/module-license-gate.d.ts +6 -1
  20. package/dist/lib/module-license-gate.js +6 -1
  21. package/dist/lib/pro-callout.d.ts +16 -10
  22. package/dist/lib/pro-callout.js +27 -4
  23. package/dist/license-set.d.ts +34 -0
  24. package/dist/license-set.js +194 -0
  25. package/dist/module-architecture.d.ts +20 -0
  26. package/dist/module-architecture.js +77 -19
  27. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  28. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  29. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  30. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  31. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  32. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  33. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  34. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  35. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  36. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  37. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  38. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  39. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  40. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  41. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  42. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  43. package/dist/parse-args.d.ts +7 -0
  44. package/dist/parse-args.js +23 -1
  45. package/dist/programmatic.d.ts +46 -0
  46. package/dist/programmatic.js +144 -19
  47. package/dist/site-chrome.d.ts +18 -7
  48. package/dist/site-chrome.js +32 -14
  49. package/dist/site-roster.d.ts +127 -0
  50. package/dist/site-roster.js +494 -0
  51. package/dist/surfaces.d.ts +20 -7
  52. package/dist/surfaces.js +149 -28
  53. package/dist/sync-routes.d.ts +9 -0
  54. package/dist/sync-routes.js +2 -2
  55. package/dist/template-manifest.d.ts +58 -2
  56. package/dist/template-manifest.js +196 -69
  57. package/package.json +6 -4
  58. package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
  59. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
  60. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
  61. package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
  62. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
  63. package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
  64. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
  65. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
  66. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
  67. package/site-templates/designs/minimalism/DESIGN.md +162 -0
  68. package/site-templates/designs/standard/DESIGN.md +162 -0
  69. package/site-templates/marketing-site/DESIGN.md +162 -0
  70. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
  71. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
  72. package/site-templates/marketing-site/ailk-template.json +1 -0
  73. package/site-templates/marketing-site-free/DESIGN.md +162 -0
  74. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
  75. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
  76. package/site-templates/marketing-site-free/ailk-template.json +3 -2
  77. package/site-templates/minimalism-site/DESIGN.md +162 -0
  78. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
  79. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
  80. package/site-templates/minimalism-site/ailk-template.json +3 -2
  81. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
  82. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
  83. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  84. package/site-templates/saas-product/DESIGN.md +162 -0
  85. package/site-templates/simple-service/DESIGN.md +162 -0
  86. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
  87. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
  88. package/site-templates/simple-service/ailk-template.json +4 -3
  89. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
  90. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
  91. package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  92. package/templates/.claude/settings.json +15 -0
  93. package/templates/.claude/skills/README.md +8 -8
  94. package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
  95. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
  96. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
  97. package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
  98. package/templates/.claude/skills/add-page/references/page-types.md +111 -0
  99. package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
  100. package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
  101. package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
  102. package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
  103. package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
  104. package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
  105. package/templates/.claude/skills/fill-page/SKILL.md +180 -0
  106. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
  107. package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
  108. package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
  109. package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
  110. package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
  111. package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
  112. package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
  113. package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
  114. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
  115. package/templates/.github/workflows/ci.yml +5 -0
  116. package/templates/CLAUDE.md +10 -5
  117. package/templates/DESIGN.md +162 -0
  118. package/templates/README.md +134 -38
  119. package/templates/apps/api/.env.example +2 -1
  120. package/templates/apps/api/package.json +1 -1
  121. package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
  122. package/templates/apps/api/src/config/index.ts +19 -6
  123. package/templates/apps/api/src/lib/license-key-store.ts +45 -0
  124. package/templates/apps/api/src/lib/site.ts +5 -3
  125. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
  126. package/templates/apps/api/src/middleware/auth.ts +9 -0
  127. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
  128. package/templates/apps/api/src/routes/content/get-page.ts +86 -2
  129. package/templates/apps/api/src/server.ts +6 -0
  130. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
  131. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
  132. package/templates/apps/mcp/package.json +2 -1
  133. package/templates/apps/mcp/src/index.ts +7 -0
  134. package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
  135. package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
  136. package/templates/apps/web/PRODUCT.md +86 -0
  137. package/templates/apps/web/__tests__/setup.ts +18 -0
  138. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
  139. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
  140. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
  141. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
  142. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
  143. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
  144. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
  145. package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
  146. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
  147. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  148. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  149. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
  150. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
  151. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
  152. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
  153. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
  154. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
  155. package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
  156. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  157. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
  158. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
  159. package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
  160. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  161. package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
  162. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  163. package/templates/apps/web/app/[locale]/layout.tsx +113 -51
  164. package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
  165. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  166. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  167. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
  168. package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
  169. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
  170. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  171. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
  172. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  173. package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
  174. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  175. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
  176. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
  177. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
  178. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
  179. package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
  180. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  181. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
  182. package/templates/apps/web/app/error-fallback.tsx +17 -45
  183. package/templates/apps/web/app/not-found.tsx +14 -38
  184. package/templates/apps/web/app/og/route.tsx +6 -0
  185. package/templates/apps/web/eslint.config.mjs +3 -0
  186. package/templates/apps/web/instrumentation.ts +14 -0
  187. package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
  188. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
  189. package/templates/apps/web/lib/license-key-store.ts +52 -0
  190. package/templates/apps/web/lib/nav-config.ts +64 -12
  191. package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
  192. package/templates/apps/web/lib/site-brand.tsx +4 -8
  193. package/templates/apps/web/lib/site-chrome.ts +3 -3
  194. package/templates/apps/web/lib/site-identity.ts +134 -0
  195. package/templates/apps/web/lib/site-theme.ts +11 -0
  196. package/templates/apps/web/messages/en.json +52 -37
  197. package/templates/apps/web/middleware.ts +1 -1
  198. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  199. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  200. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  201. package/templates/apps/web/public/favicon-16x16.png +0 -0
  202. package/templates/apps/web/public/favicon-32x32.png +0 -0
  203. package/templates/apps/web/public/favicon.ico +0 -0
  204. package/templates/apps/web/public/favicon.svg +3 -3
  205. package/templates/apps/web/public/logomark.svg +6 -3
  206. package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
  207. package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
  208. package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
  209. package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
  210. package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
  211. package/templates/apps/web/public/logos/salessmyth.svg +4 -0
  212. package/templates/apps/web/public/site.webmanifest +4 -4
  213. package/templates/apps/web/public/wordmark-light.svg +4 -0
  214. package/templates/apps/web/public/wordmark.svg +3 -3
  215. package/templates/apps/web/security-headers.config.ts +5 -1
  216. package/templates/apps/web/src/lib/commerce-sections.ts +35 -3
  217. package/templates/content/_site.mdx +41 -3
  218. package/templates/content/en/AboutPage/about.mdx +150 -144
  219. package/templates/content/en/BlogList/blog.mdx +105 -0
  220. package/templates/content/en/ContactPage/contact.mdx +44 -85
  221. package/templates/content/en/FAQPage/faq.mdx +86 -57
  222. package/templates/content/en/HomePage/home.mdx +276 -217
  223. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
  224. package/templates/content/en/SoftwareProduct/product.mdx +175 -0
  225. package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
  226. package/templates/database/CHANGELOG.md +14 -0
  227. package/templates/database/package.json +3 -2
  228. package/templates/package.json +3 -1
  229. package/templates/tools/lint-architecture.ts +315 -0
  230. package/templates/tools/tsconfig.json +14 -0
  231. package/versions.json +33 -33
  232. package/templates/.claude/skills/add-schema/SKILL.md +0 -277
  233. package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
  234. package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
  235. package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
  236. package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
  237. package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
  238. package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
  239. package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
  240. package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
  241. package/templates/apps/web/public/logo-dark.svg +0 -21
  242. package/templates/apps/web/public/logo-light.svg +0 -21
  243. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
  244. package/templates/content/en/LandingPage/blog.mdx +0 -83
  245. package/templates/content/en/LandingPage/features.mdx +0 -103
  246. package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
  247. package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
  248. package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: Standard
3
+ description: The standard design — the black-on-white foundation any shape wears by default: full navigation, a multi-column footer, the document-scroll home.
4
+ colors:
5
+ bg: "#ffffff"
6
+ fg: "#000000"
7
+ surface: "#ffffff"
8
+ surface-secondary: "#ffffff"
9
+ border: "#000000"
10
+ text-muted: "#333333"
11
+ brand: "#000000"
12
+ fg-on-brand: "#ffffff"
13
+ accent: "#000000"
14
+ bg-dark: "#000000"
15
+ fg-dark: "#ffffff"
16
+ border-dark: "#ffffff"
17
+ brand-dark: "#ffffff"
18
+ typography:
19
+ display:
20
+ fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, sans-serif"
21
+ fontSize: "3.5rem"
22
+ fontWeight: 700
23
+ lineHeight: 1.1
24
+ letterSpacing: "normal"
25
+ heading-section:
26
+ fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, sans-serif"
27
+ fontSize: "3rem"
28
+ fontWeight: 700
29
+ lineHeight: 1.1
30
+ letterSpacing: "normal"
31
+ heading-item:
32
+ fontSize: "2rem"
33
+ fontWeight: 700
34
+ lineHeight: 1.375
35
+ letterSpacing: "normal"
36
+ body:
37
+ fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, sans-serif"
38
+ fontSize: "1.125rem"
39
+ fontWeight: 400
40
+ lineHeight: 1.5
41
+ eyebrow:
42
+ fontSize: "0.875rem"
43
+ fontWeight: 600
44
+ lineHeight: 1.4
45
+ letterSpacing: "0.05em"
46
+ rounded:
47
+ button: "0.5rem"
48
+ card: "0.75rem"
49
+ spacing:
50
+ section: "7rem"
51
+ section-mobile: "4rem"
52
+ card: "1.5rem"
53
+ grid: "1.5rem"
54
+ container: "1280px"
55
+ measure: "36rem"
56
+ ---
57
+
58
+ <!--
59
+ Source template: _base (the AI Launch Kit base scaffold)
60
+ Design: standard
61
+ Version: create-ailk {{CREATE_AILK_VERSION}}
62
+
63
+ This is the `standard` design's DESIGN.md. Its one authored source is
64
+ create-ailk's site-templates/designs/standard/DESIGN.md; every template on
65
+ this design ships a copy of it that differs only in the "Source template"
66
+ line above, and a template installed with `--template <name>` replaces the
67
+ base scaffold's DESIGN.md with its own. The impeccable design skill reads
68
+ it at the start of every design session, so an agent building a new page
69
+ stays on-system.
70
+ Edit the canonical file, never a copy — a test holds every copy to it.
71
+
72
+ The tokens above describe what the site RENDERS today. Colours come from
73
+ content/_site.mdx (`theme.colors`); type, radius and spacing are platform
74
+ defaults a site cannot override yet — see "Where this is heading" below.
75
+ -->
76
+
77
+ # Standard
78
+
79
+ ## Overview
80
+
81
+ The foundation reads like a well-made wireframe: nothing on the page but the words, black on white, with rules where other sites use shadows and colour. It is the look of a site whose owner has not chosen a brand yet and does not pretend otherwise — every placeholder reads as a placeholder, and the structure of the argument is what the visitor sees. A brand is applied on top of it; it is not one.
82
+
83
+ Every page is composed from the AI Launch Kit component library (`@working-theory/ui`). Nothing is hand-built: a page is a list of registered sections, each with a bounded set of variants. Each section is one full-width band with a lot of air around it.
84
+
85
+ ## Colors
86
+
87
+ ### Primary
88
+
89
+ **Black** (`brand` `#000000`) fills the primary button; its label is white. The secondary button is white with a black hairline and black label.
90
+
91
+ ### Neutral
92
+
93
+ Pure white ground (`bg` `#ffffff`), pure black ink for headings (`fg` `#000000`), and near-black `#333333` for body copy — well above AA on white. **Borders are black** (`#000000`): card outlines, the rule under the navigation, the rules between sections and the rule above the footer are all 1px black lines. Cards are white, not tinted (`surface-secondary` is white too). There is no accent colour — `accent` is black.
94
+
95
+ Dark mode inverts: black ground, white ink, white rules, white primary button with black label.
96
+
97
+ ### Named Rules
98
+
99
+ - **Rules, not fills.** A container is drawn with a black hairline on the white ground. Grey fills are reserved for image placeholders.
100
+
101
+ ## Typography
102
+
103
+ System sans throughout; no web font.
104
+
105
+ ### Hierarchy
106
+
107
+ | Role | Size | Weight | Notes |
108
+ | --------------- | -------- | ------ | ------------------------------------------------------------------------------------- |
109
+ | Hero headline | 3.5rem | 700 | Normal tracking, line-height 1.1; 2.5rem below 1024px |
110
+ | Section heading | 3rem | 700 | Normal tracking, line-height 1.1; 2.25rem below 1024px, always under the hero |
111
+ | Item heading | 2rem | 700 | Card, feature and step titles; 1.5rem below 1024px |
112
+ | Body | 1.125rem | 400 | `#333333`, line-height 1.5; section copy capped at 36rem (about 70 characters a line) |
113
+ | Section label | 0.875rem | 600 | Uppercase, +0.05em tracking today — the small label above a section heading |
114
+
115
+ Measured from the rendered scaffold (1440px and 390px wide), not transcribed from tokens. Bold headings at normal tracking follow the foundation's pinned direction (#6493).
116
+
117
+ The small label above a section heading is **kept** as part of this foundation's voice (operator-disposed 2026-09-22) and is headed toward the treatment below.
118
+
119
+ ## Layout
120
+
121
+ Full navigation; a multi-column footer; the default home. The home-only announcement banner is off by default (`layout.announcementBanner`). Every page this site carries is listed in its roster of record, `.ailk-site-manifest.json` at the site root. Shared foundation: a 1280px container, sections at 7rem (4rem below 1024px), card grids at 1.5rem, breakpoints 320 / 768 / 1024 / 1440.
122
+
123
+ ## Shapes
124
+
125
+ Square corners everywhere (`theme.corners: square`) — buttons, cards, the auth card, image placeholders; the tokens above are the base foundation's defaults a site overrides.
126
+
127
+ ## Components
128
+
129
+ ### Buttons
130
+
131
+ Primary: black fill, white label, 0.5rem corners. Secondary: transparent with a black 1px outline and black label. Both are the library `Button`.
132
+
133
+ ### Cards / Containers
134
+
135
+ White panels with a black 1px outline, 0.75rem corners, `card` (1.5rem) padding. The one exception today: the "how it works" steps render as dark filled cards (see below).
136
+
137
+ ### Navigation
138
+
139
+ Full navigation: wordmark left, page links, an outlined and a filled button at the far right. A black hairline separates it from the page. Page links are 16px at weight 400. The two buttons are 42px tall on a 44px touch target and keep the 0.5rem button radius; square corners arrive with the platform radius change below, not in the nav alone (#6508). The Product link opens, on hover or keyboard, a 640px two-column panel whose items each show an icon, a title and a one-line description.
140
+
141
+ ## Where this is heading
142
+
143
+ The foundation's pinned direction is the pacing of a classic wireframe marketing kit — `standardissue.webflow.io` is the reference for rhythm, type scale, spacing density and section pacing only; no image, copy or code is taken from it. The four traits this section used to list as blocked now ship as settings, so the rendered site matches the reference:
144
+
145
+ | Trait | How it is set |
146
+ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
147
+ | Square corners on buttons, cards and inputs | `theme.corners: square` in `_site.mdx` |
148
+ | Section label in sentence case, bold, no tracking | `theme.eyebrowCase: sentence` |
149
+ | Solid grey image placeholders, no outline | `theme.placeholders: fill` |
150
+ | Bare check and cross marks, no circle badge | `theme.checkmarks: plain` |
151
+ | Plain, uncarded feature and step items | per section: `itemStyle: plain`, and `variant: steps` for an image beside a numbered list |
152
+ | A left hero whose headline wraps at half the container | `measure: narrow` on the hero (pricing); `gapBelow: tight` where a list follows the hero directly |
153
+ | A three-up post grid on the blog index | `blog-list` `variant: grid` (cover · date · read time · title · excerpt · Read more) |
154
+ | A plans section that leads its page | drop the hero; the pricing section owns the `<h1>` (`ownsPageTitle`) |
155
+
156
+ ## Do's and Don'ts
157
+
158
+ - **Do** keep every page built from registered sections. If a section you need does not exist, request it rather than writing one-off markup.
159
+ - **Do** change colours in `content/_site.mdx` `theme.colors`; they reach every section.
160
+ - **Don't** add a colour accent. The foundation's one idea is black on white; a brand pass adds colour deliberately, through the theme.
161
+ - **Don't** replace a black rule with a shadow or a grey fill.
162
+ - **Don't** hard-code the "heading toward" values above into a page. They arrive through the platform, or not at all.
@@ -7,11 +7,15 @@
7
7
  {{PROJECT_NAME}} — the directory name you scaffolded into
8
8
  {{COMPANY_NAME}} — the legal/marketing name of the entity behind the site
9
9
  {{COMPANY_DOMAIN}} — the canonical domain for the site (e.g. acme.example)
10
+
11
+ The tables between `generated:begin` and `generated:end` markers were
12
+ generated when this scaffold was published. They are yours to edit like the
13
+ rest of this file.
10
14
  -->
11
15
 
12
16
  # {{PROJECT_NAME}}
13
17
 
14
- Built with [AI Launch Kit](https://ailaunchkit.ai). AI Launch Kit (AILK) is an agent-first OSS template — every page emits clean schema.org JSON-LD, every server action has a matching MCP tool, and every install is one command. This site is yours; you can edit it like any other repo.
18
+ Built with [AI Launch Kit](https://ailaunchkit.ai). AI Launch Kit (AILK) is an agent-first site template — every page emits clean schema.org JSON-LD, the site carries a free MCP tool surface (`apps/mcp`) that agents can call, and every install is one command. This site is yours; you can edit it like any other repo.
15
19
 
16
20
  ## Run
17
21
 
@@ -20,7 +24,7 @@ pnpm install
20
24
  pnpm dev
21
25
  ```
22
26
 
23
- Open <http://localhost:3000>. The home page renders from `content/en/HomePage/home.mdx` with valid JSON-LD already in the `<head>`.
27
+ Open <http://localhost:3000>. The home page renders from `content/en/HomePage/home.mdx` with valid JSON-LD already in the `<head>`. New here? Start with the [Get started guide](https://ailaunchkit.ai/docs/introduction/get-started).
24
28
 
25
29
  To verify everything is wired before committing or deploying:
26
30
 
@@ -29,77 +33,169 @@ pnpm preflight # architecture + types + lint + build
29
33
  pnpm build # production build
30
34
  ```
31
35
 
32
- > Requires Node.js 20+ and `pnpm`. No vendor API keys are required for first boot — Stripe, Anthropic, and email-provider integrations are opt-in.
36
+ > Requires Node.js 20+ and `pnpm` — that is the whole prerequisite list. No database is needed to run or build this site (see [When you need a database](#when-you-need-a-database)), and no vendor API keys are required for first boot — Stripe, auth and email-provider integrations are opt-in. `pnpm install` generates the Prisma client for you; there is no generator step to remember.
33
37
 
34
38
  ## Content
35
39
 
36
- The `content/` directory is locale-prefixed: every page lives at `content/<locale>/<type>/<slug>.mdx`. The default locale is `en` (English); the AILK scaffold ships English content only. Each subdirectory under `content/en/` is a schema-typed page surface — HomePage, AboutPage, ContactPage, FAQPage, Products, Services, Projects, BlogList, BlogPosting, and friends. The filename is the slug.
40
+ The `content/` directory is locale-prefixed: every page lives at `content/<locale>/<type>/<slug>.mdx`. The default locale is `en` (English); the scaffold ships English content only. Each subdirectory under `content/en/` is a schema-typed page surface, and the filename is the slug.
41
+
42
+ The pages of the Free template, and where each one's copy lives:
43
+
44
+ <!-- generated:begin content -->
45
+
46
+ | Page | Content file | Ships in |
47
+ | ------------------- | --------------------------------------------------- | -------------------------------- |
48
+ | `/` | `content/en/HomePage/home.mdx` | every scaffold |
49
+ | `/software` | — (a code route) | every scaffold |
50
+ | `/software/[slug]` | — (a code route) | every scaffold |
51
+ | `/use-cases` | `content/en/UseCasesList/use-cases.mdx` | `--template marketing-site-free` |
52
+ | `/use-cases/[slug]` | — (a code route) | `--template marketing-site-free` |
53
+ | `/pricing` | `content/en/Pricing/pricing.mdx` | `--template marketing-site-free` |
54
+ | `/about` | `content/en/AboutPage/about.mdx` | every scaffold |
55
+ | `/faq` | `content/en/FAQPage/faq.mdx` | every scaffold |
56
+ | `/contact` | `content/en/ContactPage/contact.mdx` | every scaffold |
57
+ | `/glossary` | `content/en/GlossaryList/glossary.mdx` | `--template marketing-site-free` |
58
+ | `/newsletter` | `content/en/NewsletterSignup/newsletter.mdx` | `--template marketing-site-free` |
59
+ | `/book-a-call` | `content/en/BookCall/book-call.mdx` | `--template marketing-site-free` |
60
+ | `/sign-in` | — (a code route) | every scaffold |
61
+ | `/sign-up` | — (a code route) | every scaffold |
62
+ | `/privacy` | `content/en/PrivacyPage/privacy.mdx` | `--template marketing-site-free` |
63
+ | `/terms` | `content/en/TermsPage/terms.mdx` | `--template marketing-site-free` |
64
+ | `/purchase-policy` | `content/en/PurchasePolicyPage/purchase-policy.mdx` | every scaffold |
65
+ | `/blog` | `content/en/BlogList/blog.mdx` | every scaffold |
66
+
67
+ <!-- generated:end -->
68
+
69
+ A page marked `--template marketing-site-free` is in your site when you scaffolded with that template.
37
70
 
38
71
  To customize the home page, edit [`content/en/HomePage/home.mdx`](./content/en/HomePage/home.mdx). Save and reload the dev server. No build step.
39
72
 
40
- To add a new page, copy an existing MDX file in the matching directory and edit the frontmatter (`type`, `slug`, `title`, `description`).
73
+ To add a new page, ask Claude Code to run the `add-page` skill — it writes the route, the MDX content stub and the page's typed JSON-LD in one confirm-before-write pass. See [Skills](#skills).
41
74
 
42
75
  > `content/_site.mdx` stays at the top level (not under `content/en/`) — site identity is locale-blind.
43
76
 
44
- ## Theme
77
+ ## Make it your own
45
78
 
46
- Design tokens live in [`packages/theme`](https://www.npmjs.com/package/@working-theory/theme) (consumed from npm; no source edits needed). Components in [`packages/ui`](https://www.npmjs.com/package/@working-theory/ui) read from those tokens.
79
+ Everything that makes this site yours lives in three places. Nothing in `apps/*` or `packages/*` needs to change to rebrand, re-theme or rewrite the site.
47
80
 
48
- To swap the brand color, override the `theme.brand` token at the consumer site by extending the theme in your root layout. Example:
81
+ | To change… | Edit |
82
+ | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
83
+ | Site name, canonical URL, default locale | `content/_site.mdx` — `siteName`, `baseUrl`, `defaultLocale` |
84
+ | Who the site is (the entity answer engines read) | `content/_site.mdx` — `siteIdentity` (`kind`, `name`, `url`, `logo`, `description`, `sameAs`) |
85
+ | Colours, light and dark | `content/_site.mdx` — `theme.colors.light` / `theme.colors.dark` (`bg`, `text`, `primary`, `accent`, …) |
86
+ | Corners, section-label case, image placeholders, check marks | `content/_site.mdx` — `theme.corners`, `theme.eyebrowCase`, `theme.placeholders`, `theme.checkmarks` |
87
+ | Every page's copy and section order | `content/en/<Type>/<slug>.mdx` — the frontmatter `sections[]`; each section's `data` is its copy and its layout options |
88
+ | Header links, footer columns, social links | `apps/web/lib/nav-config.ts` — generated at scaffold, yours to edit; labels come from `apps/web/messages/<locale>.json` |
89
+ | Logo and brand assets | `apps/web/public/` and the `logo` field in `siteIdentity` |
49
90
 
50
- ```ts
51
- // app/layout.tsx (sketch)
52
- import { theme } from "@working-theory/theme";
91
+ The `theme` block is the whole design surface. Declare only what you change — an absent colour role or shape knob keeps the platform default — and the build turns it into the CSS custom properties every component reads. There is no token file to fork and no provider to wrap. The [theme and brand guide](https://ailaunchkit.ai/docs/guides/customize-theme-and-brand) is where the full walkthrough will live.
53
92
 
54
- const myTheme = {
55
- ...theme,
56
- brand: { ...theme.brand, primary: "#0a84ff" },
57
- };
58
- ```
93
+ A rebrand touches only the files in this table. If you find yourself editing a component, stop: the trait you want is either a section option (check the section's `data` fields), a theme knob, or a feature request for the [upstream library](https://github.com/working-theory-labs/ai-launch-kit).
94
+
95
+ ## Removing a page
96
+
97
+ Every page is one MDX file plus, sometimes, one nav entry:
59
98
 
60
- Apply `myTheme` via the provider you use to wrap children. The full extension surface is documented at <https://ailaunchkit.ai/theme>.
99
+ 1. Delete `content/en/<Type>/<slug>.mdx`.
100
+ 2. Remove its entry from `apps/web/lib/nav-config.ts` (header items and footer columns).
101
+ 3. Run `pnpm build`. A route directory under `apps/web/app/[locale]/` whose content is gone renders its empty state; delete the directory too if you want the URL to 404.
102
+
103
+ Modules (`docs`, `multi-tenant`, `scheduling`, …) are chosen at scaffold time with `--modules`. To drop one, scaffold again without it; there is no in-place module remover.
61
104
 
62
105
  ## Environment
63
106
 
64
- Copy `.env.example` to `.env` and fill in only what you need. Most variables are optional. A complete reference:
107
+ Copy `.env.example` to `.env`. It sets these variables:
65
108
 
66
- | Variable | Purpose |
67
- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
68
- | `AILK_LICENSE_KEY` | Optional. JWT issued by ailaunchkit.ai; verified locally to unlock Pro features. Get one at <https://ailaunchkit.ai/pro>. |
69
- | `NEXT_PUBLIC_STREAMING_AI_ENABLED` | Optional. Set to `true` to enable the opt-in streaming AI assist UI. Off by default. |
70
- | `ANTHROPIC_API_KEY` | Required only when `NEXT_PUBLIC_STREAMING_AI_ENABLED=true`. Used by the AI assist surface only. |
71
- | `AILK_LOCAL_LEAD_CAPTURE` | Optional. When `true`, leads from the contact form are written to a local file instead of an API. |
72
- | `AILK_LEADS_API_URL` | Optional. Override the upstream that receives lead-capture submissions. |
109
+ <!-- generated:begin environment -->
73
110
 
74
- See [`.env.example`](./.env.example) for the full template with comments. Per-app overrides (e.g. `apps/api/.env`) are honored alongside the root `.env`.
111
+ | Variable | Ships set to | Section of `.env.example` |
112
+ | -------------------- | ---------------- | ---------------------------- |
113
+ | `AILK_PUBLIC_DOMAIN` | `ailaunchkit.ai` | Marketing / canonical domain |
114
+ | `STEALTH` | `true` | Launch posture |
115
+ | `CONTENT_ADAPTER` | `mdx` | Content adapter |
75
116
 
76
- ## AI assist (optional)
117
+ <!-- generated:end -->
77
118
 
78
- The site ships with an opt-in streaming AI assist. It is **off by default**. To turn it on, set `NEXT_PUBLIC_STREAMING_AI_ENABLED=true` in `.env` and provide an `ANTHROPIC_API_KEY`. The assist runs server-side so the API key never reaches the browser. With both unset, the chat UI is hidden and no AI traffic flows.
119
+ Set `AILK_PUBLIC_DOMAIN` to your own domain. `STEALTH=true` keeps a fresh deploy out of search indexes and link unfurls — set it to `false` when you are ready to launch. Every other variable in `.env.example` ships commented out: each is opt-in and documented in place, next to the feature that reads it. Per-app templates (`apps/web/.env.example`, `apps/api/.env.example`, `apps/mcp/.env.example`) sit alongside their apps.
79
120
 
80
- ## Deploy
121
+ ## When you need a database
122
+
123
+ **A free marketing site needs no database at all.** `pnpm install`, `pnpm dev` and `pnpm build` all run green on an empty machine with no Postgres, no `DATABASE_URL` and no `.env` — the pages render from `content/*.mdx`, and the Prisma client is generated for you by `database`'s `postinstall` so the workspace type-checks and builds either way.
124
+
125
+ You need Postgres once you turn on one of these:
81
126
 
82
- [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/{{COMPANY_NAME}}/{{PROJECT_NAME}})
127
+ | Surface | What it stores |
128
+ | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
129
+ | **Auth** — `/sign-in`, `/sign-up`, `/account` | users, sessions, accounts |
130
+ | **Lead inbox** — `/leads` | submitted leads, for you to review signed in. Lead capture itself always appends to a local JSONL file (`LEAD_CAPTURE_FILE`) and needs nothing. |
131
+ | **Checkout returns** — `/purchase/success`, `/purchase/cancel` | the paid checkouts the Stripe webhook records. These pages serve only on a site licensed for commerce. |
132
+ | **`CONTENT_ADAPTER=database`** | pages authored in the Artifact store rather than in `content/*.mdx` |
83
133
 
84
- Or from the project root:
134
+ ### One command to get one
85
135
 
86
136
  ```bash
87
- pnpm vercel
137
+ docker run --name ailk-postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:17
88
138
  ```
89
139
 
90
- The `vercel.json` files inside `apps/web` and `apps/api` configure each surface as an independent Vercel project. Connect both, set the env vars listed above, and ship.
140
+ Point the app at it in `.env` (both variables, same URL for local work — the second is the direct connection `prisma migrate` uses):
91
141
 
92
- ## Upgrading
142
+ ```bash
143
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres
144
+ DATABASE_URL_NON_POOLED=postgresql://postgres:postgres@localhost:5432/postgres
145
+ ```
93
146
 
94
- This site consumes versioned `@working-theory/*` packages from npm. To pull upstream changes:
147
+ Then create the tables:
95
148
 
96
149
  ```bash
97
- pnpm update '@working-theory/*'
150
+ pnpm db:migrate:deploy
98
151
  ```
99
152
 
100
- That's the entire upgrade flow. There is no `git pull upstream`, no codemod, no fork-and-merge. Breaking changes ship in major-version bumps of the relevant `@working-theory/*` package; release notes call them out explicitly.
153
+ That is the whole setup. `pnpm db:studio` opens a browser UI over the data, and `pnpm db:seed` loads the sample rows. Turning the database off again is just unsetting the two variables — the marketing pages never read it.
154
+
155
+ ## Deploy
156
+
157
+ Ask Claude Code to run the `setup-deploy` skill. It writes a Vercel deploy configuration at the project root — `vercel.json`, an `.env.example` covering the variables production needs, and a `RUNBOOK.md` with the steps — and asks before overwriting any file you already have. Vercel is the one platform it supports today. `apps/api` already ships its own `vercel.json`.
158
+
159
+ The [deploy guide](https://ailaunchkit.ai/docs/guides/deploy-to-production) is where the full production walkthrough will live; until it is published, the skill's `RUNBOOK.md` is the checklist. Remember to set `STEALTH=false` on the hosting platform when you launch.
160
+
161
+ ## Going Pro
162
+
163
+ Paid levels and the commerce add-on are bought at <https://ailaunchkit.ai/pricing>; your licence key arrives by email.
164
+
165
+ Today the key is used at scaffold time. Set `AILK_LICENSE_KEY` and scaffold the paid template with the modules it needs:
166
+
167
+ ```bash
168
+ AILK_LICENSE_KEY=<your key> pnpm create ailk@latest <dir> --template <paid-template> --modules <modules>
169
+ ```
170
+
171
+ Applying a paid module to an existing site in place is not yet supported. To move this site onto a paid template, scaffold the new one and bring your `content/` directory and `content/_site.mdx` across.
172
+
173
+ ## Upgrading
174
+
175
+ This site consumes versioned `@working-theory/*` packages from npm. To take a new release:
176
+
177
+ 1. Edit the `@working-theory/*` version ranges in the `package.json` files that list them to the release you want.
178
+ 2. Run `pnpm install`.
179
+ 3. Run `pnpm create ailk@latest sync-routes` in the site root. It reports the route directories the template gained since you scaffolded; add `--apply` to copy them in. A route you have changed is never overwritten, and nothing is deleted.
180
+
181
+ Change the ranges by hand. Below 1.0 a caret range (`^0.26.0`) never crosses a minor version, so updating within the ranges you already have silently stays on the old release. There is no `git pull upstream`, no fork-and-merge. Breaking changes are announced in each package's `CHANGELOG.md` in the [upstream repo](https://github.com/working-theory-labs/ai-launch-kit).
182
+
183
+ ## Skills
184
+
185
+ This project ships Claude Code skills in `.claude/skills/`. Claude Code discovers them at session start — ask for one by name.
186
+
187
+ <!-- generated:begin skills -->
188
+
189
+ | Skill | What it does |
190
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
191
+ | `add-page` | Generate a new page: App Router route + MDX content stub + typed JSON-LD, in one confirm-before-write pass. Also documents the `schema:` frontmatter override for adding supplementary structured data to a page that already exists. |
192
+ | `add-auth` | Activate auth providers — magic-link via Resend, Google OAuth, GitHub OAuth. |
193
+ | `setup-deploy` | Emit deploy configuration — `vercel.json`, `.env.example`, and `RUNBOOK.md` — for a Vercel deploy. |
194
+ | `check-site` | Run `ailk-audit --json` and explain the score and findings in plain language. |
195
+ | `fill-page` | Fill one page's section content from a category profile: labelled sample content (tier 1) or your own first draft from a short intake (tier 2). |
196
+ | `upgrade-plan` | Compare Free, Pro, Pro Plus, Agency and the commerce add-on side by side, with the prices the public pricing page publishes right now, and recommend one option for this site with a reason. |
101
197
 
102
- If you need to override a section component, swap its registration in the section registry (see <https://ailaunchkit.ai/sections>). Framework code stays out of your tree.
198
+ <!-- generated:end -->
103
199
 
104
200
  ## License & contributing
105
201
 
@@ -17,9 +17,10 @@ PORT=3002
17
17
  FRONTEND_URL=http://localhost:3000
18
18
 
19
19
  # =============================================================================
20
- # Local Database (REQUIRED)
20
+ # Local Database (required in production)
21
21
  # =============================================================================
22
22
  # For auth only: User, Session, ApiToken, etc.
23
+ # Unset outside production, the API starts and authed routes answer 503.
23
24
  DATABASE_URL="postgresql://user:password@localhost:5432/customer_template"
24
25
 
25
26
  # =============================================================================
@@ -6,7 +6,7 @@
6
6
  "license": "Apache-2.0",
7
7
  "type": "module",
8
8
  "scripts": {
9
- "dev": "tsx watch src/cli.ts",
9
+ "dev": "tsx watch --exclude \"../../node_modules/**\" src/cli.ts",
10
10
  "abandon:sweep": "tsx src/bin/abandon-sweep.ts",
11
11
  "followup:sweep": "tsx src/bin/followup-sweep.ts",
12
12
  "listing-csv:sweep": "tsx src/bin/listing-csv-sweep.ts",
@@ -5,10 +5,11 @@ jest.mock('dotenv', () => ({
5
5
 
6
6
  // Mock the observability logger so assertions don't depend on pino's stdout serialization.
7
7
  const mockLoggerError = jest.fn();
8
+ const mockLoggerWarn = jest.fn();
8
9
  jest.mock('@working-theory/observability', () => ({
9
10
  logger: {
10
11
  error: (...args: unknown[]) => mockLoggerError(...args),
11
- warn: jest.fn(),
12
+ warn: (...args: unknown[]) => mockLoggerWarn(...args),
12
13
  info: jest.fn(),
13
14
  debug: jest.fn(),
14
15
  },
@@ -41,8 +42,9 @@ describe('Config', () => {
41
42
  expect(dotenv.config).toHaveBeenCalled();
42
43
  });
43
44
 
44
- it('should exit on missing DATABASE_URL', () => {
45
+ it('should exit on missing DATABASE_URL in production', () => {
45
46
  // ARRANGE
47
+ process.env.NODE_ENV = 'production';
46
48
  delete process.env.DATABASE_URL;
47
49
 
48
50
  const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
@@ -61,6 +63,39 @@ describe('Config', () => {
61
63
  exitSpy.mockRestore();
62
64
  });
63
65
 
66
+ // #6482 — outside production a missing DATABASE_URL starts the API
67
+ // database-less (one warning) instead of exiting, so a fresh scaffold's
68
+ // `pnpm dev` keeps its "no database needed" promise.
69
+ it.each([
70
+ ['development', 'development'],
71
+ ['unset', undefined],
72
+ ['test', 'test'],
73
+ ])('should start database-less with DATABASE_URL unset when NODE_ENV is %s', (_label, nodeEnv) => {
74
+ // ARRANGE
75
+ if (nodeEnv === undefined) {
76
+ delete process.env.NODE_ENV;
77
+ } else {
78
+ process.env.NODE_ENV = nodeEnv;
79
+ }
80
+ delete process.env.DATABASE_URL;
81
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
82
+ throw new Error('Process exit called');
83
+ });
84
+
85
+ // ACT
86
+ const { config } = require('../index');
87
+
88
+ // ASSERT
89
+ expect(exitSpy).not.toHaveBeenCalled();
90
+ expect(config.DATABASE_URL).toBeUndefined();
91
+ expect(config.NODE_ENV).toBe(nodeEnv ?? 'development');
92
+ expect(mockLoggerError).not.toHaveBeenCalled();
93
+ expect(mockLoggerWarn).toHaveBeenCalledTimes(1);
94
+ expect(mockLoggerWarn.mock.calls[0]?.[1]).toContain('database_not_configured');
95
+
96
+ exitSpy.mockRestore();
97
+ });
98
+
64
99
  it('should not require any external-platform forwarding env vars', () => {
65
100
  // ARRANGE — only DATABASE_URL set; legacy forwarding vars explicitly absent
66
101
  process.env.DATABASE_URL = 'postgresql://test:test@localhost/test';
@@ -15,8 +15,9 @@ export interface Config {
15
15
  PORT: number;
16
16
  FRONTEND_URL: string;
17
17
 
18
- // Local Database (for auth: User, Session, ApiToken, etc.)
19
- DATABASE_URL: string;
18
+ // Local Database (for auth: User, Session, ApiToken, etc.). Required in
19
+ // production; unset elsewhere starts the API database-less (#6482).
20
+ DATABASE_URL?: string;
20
21
 
21
22
  // Stripe — consumed by apps/api/src/lib/stripe.ts via @working-theory/stripe-client wrapper
22
23
  STRIPE_SECRET_KEY?: string;
@@ -93,10 +94,20 @@ export interface Config {
93
94
 
94
95
  function validateConfig(): Config {
95
96
  const errors: string[] = [];
97
+ const nodeEnv = (process.env.NODE_ENV || 'development') as Config['NODE_ENV'];
96
98
 
97
- // Required: Local database for auth
99
+ // Local database for auth: required in production. Elsewhere a fresh
100
+ // scaffold runs with no .env, so start database-less and let the auth
101
+ // preHandler answer 503 database_not_configured (#6482).
98
102
  if (!process.env.DATABASE_URL) {
99
- errors.push('DATABASE_URL is required for authentication');
103
+ if (nodeEnv === 'production') {
104
+ errors.push('DATABASE_URL is required for authentication');
105
+ } else {
106
+ logger.warn(
107
+ { nodeEnv },
108
+ '⚠️ DATABASE_URL not set — starting database-less; authenticated routes answer 503 database_not_configured',
109
+ );
110
+ }
100
111
  }
101
112
 
102
113
  // Exit on errors
@@ -106,11 +117,13 @@ function validateConfig(): Config {
106
117
  }
107
118
 
108
119
  return {
109
- NODE_ENV: (process.env.NODE_ENV || 'development') as Config['NODE_ENV'],
120
+ NODE_ENV: nodeEnv,
110
121
  PORT: parseInt(process.env.PORT || '3002', 10),
111
122
  FRONTEND_URL: process.env.FRONTEND_URL || 'http://localhost:3000',
112
123
 
113
- DATABASE_URL: process.env.DATABASE_URL!,
124
+ ...(process.env.DATABASE_URL !== undefined && {
125
+ DATABASE_URL: process.env.DATABASE_URL,
126
+ }),
114
127
 
115
128
  ...(process.env.STRIPE_SECRET_KEY !== undefined && {
116
129
  STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Registers the durable home of a renewed licence key (#6677 DD-6).
3
+ *
4
+ * `@working-theory/license` renews the deployment's key before it lapses and
5
+ * keeps the renewed key in a `LicenseKeyStore`. Its default store is a file,
6
+ * which a Vercel redeploy wipes — so a redeployed site would fall back to the
7
+ * older `AILK_LICENSE_KEY` and, past the renewal grace, to free. With a
8
+ * database configured, this registers the store backed by ONE deployment-wide
9
+ * `SiteSetting` row, `(DEFAULT_WORKSPACE_ID, "license.renewed_key")`, shared
10
+ * by this app, apps/mcp and apps/web.
11
+ *
12
+ * The row is deployment-wide, not tenant data: the licence is the
13
+ * deployment's, so it sits under the seeded default workspace in single- and
14
+ * multi-tenant builds alike (the named tenant-scope exclusion in
15
+ * architecture.yaml). A DB-less scaffold keeps the file store.
16
+ */
17
+
18
+ import { DEFAULT_WORKSPACE_ID } from "@working-theory/database/tenancy";
19
+ import {
20
+ createSiteSettingLicenseKeyStore,
21
+ setLicenseKeyStore,
22
+ } from "@working-theory/license";
23
+ import { logger } from "@working-theory/observability";
24
+
25
+ import { prisma } from "./prisma.js";
26
+
27
+ /**
28
+ * Register the SiteSetting-backed store when `DATABASE_URL` is set. An
29
+ * explicitly set `AILK_LICENSE_STORAGE_PATH` is the operator choosing the
30
+ * file store, and is honoured. Never throws: a failure logs and leaves the
31
+ * file store in place.
32
+ */
33
+ export async function registerLicenseKeyStore(): Promise<void> {
34
+ if (!process.env.DATABASE_URL || process.env.AILK_LICENSE_STORAGE_PATH) return;
35
+ try {
36
+ setLicenseKeyStore(
37
+ createSiteSettingLicenseKeyStore(prisma.siteSetting, DEFAULT_WORKSPACE_ID),
38
+ );
39
+ } catch (err) {
40
+ logger.warn(
41
+ { errName: err instanceof Error ? err.name : "UnknownError" },
42
+ "[license] could not register the SiteSetting licence-key store — the file store stays in place",
43
+ );
44
+ }
45
+ }
@@ -144,9 +144,11 @@ export async function resolveWorkspaceIdForAlias(
144
144
  * 1. **No `DATABASE_URL` configured** — the literal `requireDb()` guard
145
145
  * `apps/api/src/services/flow-engine.ts` uses for the flow services:
146
146
  * presence, not reachability. (`apps/api/src/config/index.ts`'s own
147
- * eager `validateConfig()` already refuses a real `apps/api` boot with
148
- * `DATABASE_URL` unset before `build()` is ever reached, so this arm is
149
- * for any OTHER caller of `build()`.)
147
+ * eager `validateConfig()` refuses a **production** `apps/api` boot with
148
+ * `DATABASE_URL` unset before `build()` is ever reached; outside
149
+ * production a real boot starts database-less (#6482), so this arm is
150
+ * reachable from a real dev boot as well as from any other caller of
151
+ * `build()`.)
150
152
  * 2. **`prisma.workspace.findUnique` is not even a function** — the case
151
153
  * that surfaced this arm: `apps/api`'s shared/global prisma mocks
152
154
  * (`lib/__mocks__/prisma-client.js`, the module-map-level double every