create-ailk 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 (533) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +39 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +80 -0
  5. package/dist/copy.d.ts +8 -0
  6. package/dist/copy.js +109 -0
  7. package/dist/index.d.ts +10 -0
  8. package/dist/index.js +10 -0
  9. package/dist/lib/pro-callout.d.ts +12 -0
  10. package/dist/lib/pro-callout.js +20 -0
  11. package/dist/parse-args.d.ts +13 -0
  12. package/dist/parse-args.js +81 -0
  13. package/dist/programmatic.d.ts +49 -0
  14. package/dist/programmatic.js +89 -0
  15. package/dist/substitute.d.ts +31 -0
  16. package/dist/substitute.js +45 -0
  17. package/dist/validate.d.ts +8 -0
  18. package/dist/validate.js +70 -0
  19. package/package.json +47 -0
  20. package/templates/.claude/.gitkeep +0 -0
  21. package/templates/.claude/agents/web.md +80 -0
  22. package/templates/.claude/rules/architecture.md +62 -0
  23. package/templates/.claude/rules/preserve-existing-style.md +51 -0
  24. package/templates/.claude/skills/README.md +43 -0
  25. package/templates/.claude/skills/add-schema/SKILL.md +270 -0
  26. package/templates/.claude/skills/add-schema/references/conflict-handling.md +200 -0
  27. package/templates/.claude/skills/add-schema/references/file-finding.md +123 -0
  28. package/templates/.claude/skills/add-schema/references/schema-types.md +131 -0
  29. package/templates/.claude/skills/audit-site/SKILL.md +213 -0
  30. package/templates/.claude/skills/audit-site/references/common-errors.md +113 -0
  31. package/templates/.claude/skills/audit-site/references/findings-glossary.md +138 -0
  32. package/templates/.claude/skills/audit-site/references/severity-levels.md +114 -0
  33. package/templates/.claude/skills/provision-config/SKILL.md +243 -0
  34. package/templates/.claude/skills/provision-config/references/env-vars-template.md +184 -0
  35. package/templates/.claude/skills/provision-config/references/runbook-template.md +248 -0
  36. package/templates/.claude/skills/provision-config/references/vercel-json-template.md +98 -0
  37. package/templates/.claude/skills/scaffold-auth/SKILL.md +261 -0
  38. package/templates/.claude/skills/scaffold-auth/references/env-vars.md +91 -0
  39. package/templates/.claude/skills/scaffold-auth/references/oauth-app-setup.md +171 -0
  40. package/templates/.claude/skills/scaffold-commerce/SKILL.md +310 -0
  41. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +71 -0
  42. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +83 -0
  43. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +72 -0
  44. package/templates/.claude/skills/scaffold-page/SKILL.md +243 -0
  45. package/templates/.claude/skills/scaffold-page/references/page-types.md +169 -0
  46. package/templates/.claude/skills/scaffold-page/templates/content.mdx.tmpl +28 -0
  47. package/templates/.claude/skills/scaffold-page/templates/route.tsx.tmpl +28 -0
  48. package/templates/.claude/skills/suggest-site-pages/SKILL.md +242 -0
  49. package/templates/.claude/skills/suggest-site-pages/references/categories.md +287 -0
  50. package/templates/.env.example +205 -0
  51. package/templates/.gitkeep +2 -0
  52. package/templates/CLAUDE.md +142 -0
  53. package/templates/CONVENTIONS.md +222 -0
  54. package/templates/README.md +110 -0
  55. package/templates/apps/api/.env.example +40 -0
  56. package/templates/apps/api/CLAUDE.md +63 -0
  57. package/templates/apps/api/api/index.ts +144 -0
  58. package/templates/apps/api/jest.config.cjs +74 -0
  59. package/templates/apps/api/package.json +54 -0
  60. package/templates/apps/api/src/__tests__/factories/content-fixture.ts +138 -0
  61. package/templates/apps/api/src/__tests__/factories/index.ts +70 -0
  62. package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +82 -0
  63. package/templates/apps/api/src/__tests__/index.test.ts +118 -0
  64. package/templates/apps/api/src/__tests__/server.test.ts +207 -0
  65. package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +126 -0
  66. package/templates/apps/api/src/__tests__/setup.ts +24 -0
  67. package/templates/apps/api/src/cli.ts +30 -0
  68. package/templates/apps/api/src/config/__tests__/index.test.ts +97 -0
  69. package/templates/apps/api/src/config/index.ts +56 -0
  70. package/templates/apps/api/src/index.ts +32 -0
  71. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +15 -0
  72. package/templates/apps/api/src/lib/__mocks__/prisma.ts +45 -0
  73. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +178 -0
  74. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +100 -0
  75. package/templates/apps/api/src/lib/__tests__/errors.test.ts +94 -0
  76. package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +144 -0
  77. package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +8 -0
  78. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +227 -0
  79. package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +125 -0
  80. package/templates/apps/api/src/lib/__tests__/prisma.test.ts +165 -0
  81. package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +129 -0
  82. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +74 -0
  83. package/templates/apps/api/src/lib/content-adapter.ts +86 -0
  84. package/templates/apps/api/src/lib/error-serializer.ts +77 -0
  85. package/templates/apps/api/src/lib/errors.ts +95 -0
  86. package/templates/apps/api/src/lib/file-fallback.ts +87 -0
  87. package/templates/apps/api/src/lib/lead-capture-store.ts +110 -0
  88. package/templates/apps/api/src/lib/locales.ts +201 -0
  89. package/templates/apps/api/src/lib/outbound-http.ts +122 -0
  90. package/templates/apps/api/src/lib/prisma.ts +21 -0
  91. package/templates/apps/api/src/lib/require-paid-tier.ts +53 -0
  92. package/templates/apps/api/src/lib/stripe.ts +30 -0
  93. package/templates/apps/api/src/lib/webhook-idempotency.ts +92 -0
  94. package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +111 -0
  95. package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +172 -0
  96. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +119 -0
  97. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +138 -0
  98. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +149 -0
  99. package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +139 -0
  100. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +175 -0
  101. package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +76 -0
  102. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +153 -0
  103. package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +72 -0
  104. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +292 -0
  105. package/templates/apps/api/src/middleware/auth.ts +246 -0
  106. package/templates/apps/api/src/middleware/rate-limit.ts +132 -0
  107. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +129 -0
  108. package/templates/apps/api/src/openapi/index.ts +12 -0
  109. package/templates/apps/api/src/openapi/spec.ts +408 -0
  110. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +193 -0
  111. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +201 -0
  112. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +294 -0
  113. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +486 -0
  114. package/templates/apps/api/src/routes/auth/tokens.ts +199 -0
  115. package/templates/apps/api/src/routes/auth/user.ts +244 -0
  116. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +588 -0
  117. package/templates/apps/api/src/routes/checkout/index.ts +13 -0
  118. package/templates/apps/api/src/routes/checkout/sessions.ts +185 -0
  119. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +239 -0
  120. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +201 -0
  121. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +141 -0
  122. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +122 -0
  123. package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +44 -0
  124. package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +118 -0
  125. package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +51 -0
  126. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +252 -0
  127. package/templates/apps/api/src/routes/content/create.ts +114 -0
  128. package/templates/apps/api/src/routes/content/delete.ts +81 -0
  129. package/templates/apps/api/src/routes/content/get-page.ts +98 -0
  130. package/templates/apps/api/src/routes/content/index.ts +32 -0
  131. package/templates/apps/api/src/routes/content/list-pages.ts +83 -0
  132. package/templates/apps/api/src/routes/content/list-types.ts +19 -0
  133. package/templates/apps/api/src/routes/content/rate-limit.ts +13 -0
  134. package/templates/apps/api/src/routes/content/slug.ts +9 -0
  135. package/templates/apps/api/src/routes/content/update.ts +111 -0
  136. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +415 -0
  137. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +165 -0
  138. package/templates/apps/api/src/routes/leads/index.ts +130 -0
  139. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +296 -0
  140. package/templates/apps/api/src/routes/test/auth/session.ts +141 -0
  141. package/templates/apps/api/src/routes/webhooks/README.md +57 -0
  142. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +247 -0
  143. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +198 -0
  144. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +633 -0
  145. package/templates/apps/api/src/routes/webhooks/index.ts +41 -0
  146. package/templates/apps/api/src/routes/webhooks/stripe.ts +253 -0
  147. package/templates/apps/api/src/server.ts +157 -0
  148. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +395 -0
  149. package/templates/apps/api/src/services/enrichment-shim.ts +53 -0
  150. package/templates/apps/api/src/services/lead-routing.ts +338 -0
  151. package/templates/apps/api/tsconfig.json +34 -0
  152. package/templates/apps/api/vercel.json +17 -0
  153. package/templates/apps/mcp/.env.example +52 -0
  154. package/templates/apps/mcp/CLAUDE.md +84 -0
  155. package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +52 -0
  156. package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +46 -0
  157. package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +36 -0
  158. package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +54 -0
  159. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +110 -0
  160. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +213 -0
  161. package/templates/apps/mcp/__tests__/audit.test.ts +217 -0
  162. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +52 -0
  163. package/templates/apps/mcp/__tests__/create_page.test.ts +201 -0
  164. package/templates/apps/mcp/__tests__/delete_page.test.ts +176 -0
  165. package/templates/apps/mcp/__tests__/get_business_hours.test.ts +82 -0
  166. package/templates/apps/mcp/__tests__/get_faq.test.ts +80 -0
  167. package/templates/apps/mcp/__tests__/get_locations.test.ts +65 -0
  168. package/templates/apps/mcp/__tests__/get_offerings.test.ts +90 -0
  169. package/templates/apps/mcp/__tests__/get_pricing.test.ts +80 -0
  170. package/templates/apps/mcp/__tests__/get_site_info.test.ts +81 -0
  171. package/templates/apps/mcp/__tests__/http-transport.test.ts +323 -0
  172. package/templates/apps/mcp/__tests__/lib/errors.test.ts +75 -0
  173. package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +75 -0
  174. package/templates/apps/mcp/__tests__/list_pages.test.ts +112 -0
  175. package/templates/apps/mcp/__tests__/locale.test.ts +135 -0
  176. package/templates/apps/mcp/__tests__/parity.test.ts +205 -0
  177. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +34 -0
  178. package/templates/apps/mcp/__tests__/read_content.test.ts +153 -0
  179. package/templates/apps/mcp/__tests__/request_callback.test.ts +163 -0
  180. package/templates/apps/mcp/__tests__/search_content.test.ts +153 -0
  181. package/templates/apps/mcp/__tests__/server.test.ts +115 -0
  182. package/templates/apps/mcp/__tests__/submit_lead.test.ts +165 -0
  183. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +161 -0
  184. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +227 -0
  185. package/templates/apps/mcp/__tests__/tools/scaffold.test.ts +237 -0
  186. package/templates/apps/mcp/__tests__/update_page.test.ts +195 -0
  187. package/templates/apps/mcp/jest.config.cjs +82 -0
  188. package/templates/apps/mcp/package.json +37 -0
  189. package/templates/apps/mcp/src/abuse/daily-cap.ts +47 -0
  190. package/templates/apps/mcp/src/abuse/email-validator.ts +25 -0
  191. package/templates/apps/mcp/src/abuse/honeypot.ts +21 -0
  192. package/templates/apps/mcp/src/abuse/index.ts +111 -0
  193. package/templates/apps/mcp/src/abuse/user-agent-deny.ts +58 -0
  194. package/templates/apps/mcp/src/http-transport.ts +303 -0
  195. package/templates/apps/mcp/src/index.ts +67 -0
  196. package/templates/apps/mcp/src/lib/audit-single-page.ts +194 -0
  197. package/templates/apps/mcp/src/lib/errors.ts +61 -0
  198. package/templates/apps/mcp/src/lib/locales.ts +171 -0
  199. package/templates/apps/mcp/src/lib/require-paid-tier.ts +44 -0
  200. package/templates/apps/mcp/src/lib/resolve-locale.ts +30 -0
  201. package/templates/apps/mcp/src/server.ts +139 -0
  202. package/templates/apps/mcp/src/tools/audit.ts +103 -0
  203. package/templates/apps/mcp/src/tools/capture_lead.ts +52 -0
  204. package/templates/apps/mcp/src/tools/create_page.ts +95 -0
  205. package/templates/apps/mcp/src/tools/delete_page.ts +73 -0
  206. package/templates/apps/mcp/src/tools/get_business_hours.ts +66 -0
  207. package/templates/apps/mcp/src/tools/get_faq.ts +69 -0
  208. package/templates/apps/mcp/src/tools/get_locations.ts +85 -0
  209. package/templates/apps/mcp/src/tools/get_offerings.ts +66 -0
  210. package/templates/apps/mcp/src/tools/get_pricing.ts +74 -0
  211. package/templates/apps/mcp/src/tools/get_site_info.ts +84 -0
  212. package/templates/apps/mcp/src/tools/index.ts +125 -0
  213. package/templates/apps/mcp/src/tools/list_pages.ts +72 -0
  214. package/templates/apps/mcp/src/tools/purchase.ts +89 -0
  215. package/templates/apps/mcp/src/tools/read_content.ts +61 -0
  216. package/templates/apps/mcp/src/tools/request_callback.ts +51 -0
  217. package/templates/apps/mcp/src/tools/scaffold.ts +119 -0
  218. package/templates/apps/mcp/src/tools/search_content.ts +59 -0
  219. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +47 -0
  220. package/templates/apps/mcp/src/tools/update_page.ts +91 -0
  221. package/templates/apps/mcp/tsconfig.json +19 -0
  222. package/templates/apps/web/.env.example +47 -0
  223. package/templates/apps/web/CLAUDE.md +81 -0
  224. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +94 -0
  225. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +75 -0
  226. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +82 -0
  227. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +168 -0
  228. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +208 -0
  229. package/templates/apps/web/__tests__/better-stack.test.ts +66 -0
  230. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +87 -0
  231. package/templates/apps/web/__tests__/blog-source-populated.test.ts +130 -0
  232. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +60 -0
  233. package/templates/apps/web/__tests__/google-analytics.test.tsx +104 -0
  234. package/templates/apps/web/__tests__/i18n/middleware.test.ts +119 -0
  235. package/templates/apps/web/__tests__/i18n/routing.test.ts +114 -0
  236. package/templates/apps/web/__tests__/i18n/translations.test.ts +191 -0
  237. package/templates/apps/web/__tests__/json-ld.test.ts +98 -0
  238. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +580 -0
  239. package/templates/apps/web/__tests__/layout-stealth.test.ts +39 -0
  240. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +103 -0
  241. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +114 -0
  242. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +94 -0
  243. package/templates/apps/web/__tests__/page.test.tsx +230 -0
  244. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +321 -0
  245. package/templates/apps/web/__tests__/projects-routes.test.tsx +392 -0
  246. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +475 -0
  247. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +53 -0
  248. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +191 -0
  249. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +123 -0
  250. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +52 -0
  251. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +78 -0
  252. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +203 -0
  253. package/templates/apps/web/__tests__/section-routes.test.tsx +693 -0
  254. package/templates/apps/web/__tests__/security-headers.test.ts +201 -0
  255. package/templates/apps/web/__tests__/sentry-config.test.ts +116 -0
  256. package/templates/apps/web/__tests__/setup.ts +82 -0
  257. package/templates/apps/web/__tests__/sitemap-locales.test.ts +415 -0
  258. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +844 -0
  259. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +81 -0
  260. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +110 -0
  261. package/templates/apps/web/app/.well-known/mcp.json/route.ts +58 -0
  262. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +120 -0
  263. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +202 -0
  264. package/templates/apps/web/app/[locale]/about/page.tsx +31 -0
  265. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +173 -0
  266. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +45 -0
  267. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +117 -0
  268. package/templates/apps/web/app/[locale]/careers/page.tsx +25 -0
  269. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +44 -0
  270. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +113 -0
  271. package/templates/apps/web/app/[locale]/case-studies/page.tsx +25 -0
  272. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +63 -0
  273. package/templates/apps/web/app/[locale]/clients/page.tsx +27 -0
  274. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +130 -0
  275. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +152 -0
  276. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +92 -0
  277. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +79 -0
  278. package/templates/apps/web/app/[locale]/contact/error.tsx +24 -0
  279. package/templates/apps/web/app/[locale]/contact/page.tsx +38 -0
  280. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +34 -0
  281. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +119 -0
  282. package/templates/apps/web/app/[locale]/courses/page.tsx +25 -0
  283. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +160 -0
  284. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +25 -0
  285. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +117 -0
  286. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +58 -0
  287. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +118 -0
  288. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +72 -0
  289. package/templates/apps/web/app/[locale]/downloads/page.tsx +25 -0
  290. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +47 -0
  291. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +130 -0
  292. package/templates/apps/web/app/[locale]/events/page.tsx +25 -0
  293. package/templates/apps/web/app/[locale]/faq/page.tsx +36 -0
  294. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +34 -0
  295. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +78 -0
  296. package/templates/apps/web/app/[locale]/how-to/page.tsx +40 -0
  297. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +43 -0
  298. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +129 -0
  299. package/templates/apps/web/app/[locale]/integrations/page.tsx +25 -0
  300. package/templates/apps/web/app/[locale]/launch-service/page.tsx +31 -0
  301. package/templates/apps/web/app/[locale]/layout.tsx +241 -0
  302. package/templates/apps/web/app/[locale]/page.tsx +41 -0
  303. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +44 -0
  304. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +113 -0
  305. package/templates/apps/web/app/[locale]/partners/page.tsx +25 -0
  306. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +43 -0
  307. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +129 -0
  308. package/templates/apps/web/app/[locale]/products/page.tsx +25 -0
  309. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +39 -0
  310. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +138 -0
  311. package/templates/apps/web/app/[locale]/projects/page.tsx +30 -0
  312. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +51 -0
  313. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +117 -0
  314. package/templates/apps/web/app/[locale]/resources/page.tsx +27 -0
  315. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +43 -0
  316. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +129 -0
  317. package/templates/apps/web/app/[locale]/services/page.tsx +25 -0
  318. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +34 -0
  319. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +81 -0
  320. package/templates/apps/web/app/[locale]/software/page.tsx +39 -0
  321. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +34 -0
  322. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +82 -0
  323. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +39 -0
  324. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +63 -0
  325. package/templates/apps/web/app/[locale]/testimonials/page.tsx +27 -0
  326. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +63 -0
  327. package/templates/apps/web/app/[locale]/use-cases/page.tsx +27 -0
  328. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +34 -0
  329. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +82 -0
  330. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +39 -0
  331. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +152 -0
  332. package/templates/apps/web/app/api/auth/[...all]/route.ts +16 -0
  333. package/templates/apps/web/app/error-fallback.tsx +75 -0
  334. package/templates/apps/web/app/error.tsx +23 -0
  335. package/templates/apps/web/app/layout.tsx +18 -0
  336. package/templates/apps/web/app/llms.txt/route.ts +101 -0
  337. package/templates/apps/web/app/not-found.tsx +48 -0
  338. package/templates/apps/web/app/og/__tests__/route.test.ts +164 -0
  339. package/templates/apps/web/app/og/route.tsx +135 -0
  340. package/templates/apps/web/app/robots.txt/route.ts +36 -0
  341. package/templates/apps/web/app/sitemap.xml/route.ts +146 -0
  342. package/templates/apps/web/auth.ts +141 -0
  343. package/templates/apps/web/components/ThemeToggle.tsx +60 -0
  344. package/templates/apps/web/components/google-analytics.tsx +34 -0
  345. package/templates/apps/web/content/en/blog/index.mdx +15 -0
  346. package/templates/apps/web/content/en/blog/launching-ailk.mdx +20 -0
  347. package/templates/apps/web/content/en/docs/getting-started.mdx +30 -0
  348. package/templates/apps/web/i18n/request.ts +36 -0
  349. package/templates/apps/web/jest.config.cjs +100 -0
  350. package/templates/apps/web/lib/__mocks__/next-mdx-remote-rsc.js +12 -0
  351. package/templates/apps/web/lib/__mocks__/source-server.js +31 -0
  352. package/templates/apps/web/lib/__tests__/og.test.ts +65 -0
  353. package/templates/apps/web/lib/auth-env.ts +66 -0
  354. package/templates/apps/web/lib/blog-list-data.ts +95 -0
  355. package/templates/apps/web/lib/blog-post-data.ts +79 -0
  356. package/templates/apps/web/lib/nav-config.ts +13 -0
  357. package/templates/apps/web/lib/observability/better-stack.ts +27 -0
  358. package/templates/apps/web/lib/og.ts +23 -0
  359. package/templates/apps/web/lib/prisma-for-auth.ts +34 -0
  360. package/templates/apps/web/lib/source.ts +34 -0
  361. package/templates/apps/web/messages/en.json +131 -0
  362. package/templates/apps/web/middleware.ts +80 -0
  363. package/templates/apps/web/next.config.mjs +167 -0
  364. package/templates/apps/web/package.json +62 -0
  365. package/templates/apps/web/public/.well-known/security.txt +17 -0
  366. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  367. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  368. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  369. package/templates/apps/web/public/favicon-16x16.png +0 -0
  370. package/templates/apps/web/public/favicon-32x32.png +0 -0
  371. package/templates/apps/web/public/favicon.ico +0 -0
  372. package/templates/apps/web/public/favicon.svg +4 -0
  373. package/templates/apps/web/public/logo-dark.svg +21 -0
  374. package/templates/apps/web/public/logo-light.svg +21 -0
  375. package/templates/apps/web/public/logomark-dark.svg +4 -0
  376. package/templates/apps/web/public/logomark-light.svg +4 -0
  377. package/templates/apps/web/public/og-default.png +0 -0
  378. package/templates/apps/web/public/site.webmanifest +19 -0
  379. package/templates/apps/web/scripts/assert-content-bundled.ts +55 -0
  380. package/templates/apps/web/security-headers.config.ts +132 -0
  381. package/templates/apps/web/sentry.client.config.ts +27 -0
  382. package/templates/apps/web/sentry.edge.config.ts +15 -0
  383. package/templates/apps/web/sentry.server.config.ts +20 -0
  384. package/templates/apps/web/source.config.ts +78 -0
  385. package/templates/apps/web/src/components/SiteNav.tsx +21 -0
  386. package/templates/apps/web/src/lib/routes.ts +37 -0
  387. package/templates/apps/web/tsconfig.json +35 -0
  388. package/templates/apps/web/types/messages.d.ts +19 -0
  389. package/templates/content/.gitkeep +0 -0
  390. package/templates/content/CLAUDE.md +85 -0
  391. package/templates/content/_site.mdx +38 -0
  392. package/templates/content/ar/HomePage/home.mdx +123 -0
  393. package/templates/content/ar/_site.mdx +35 -0
  394. package/templates/content/de/_site.mdx +29 -0
  395. package/templates/content/en/.gitkeep +0 -0
  396. package/templates/content/en/AboutPage/about.mdx +19 -0
  397. package/templates/content/en/AboutPage/about.schema.json +82 -0
  398. package/templates/content/en/BookCall/book-call.mdx +19 -0
  399. package/templates/content/en/BookCall/book-call.schema.json +121 -0
  400. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +20 -0
  401. package/templates/content/en/CareersList/careers.mdx +24 -0
  402. package/templates/content/en/CaseStudiesList/case-studies.mdx +22 -0
  403. package/templates/content/en/CaseStudiesList/showcase.mdx +8 -0
  404. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +32 -0
  405. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +41 -0
  406. package/templates/content/en/ClientsList/clients.mdx +38 -0
  407. package/templates/content/en/ContactPage/contact.mdx +19 -0
  408. package/templates/content/en/ContactPage/contact.schema.json +100 -0
  409. package/templates/content/en/ContactPoint/primary.mdx +14 -0
  410. package/templates/content/en/Course/aeo-fundamentals.mdx +37 -0
  411. package/templates/content/en/Course/schema-builder-workshop.mdx +62 -0
  412. package/templates/content/en/CoursesList/courses.mdx +22 -0
  413. package/templates/content/en/DownloadsList/downloads.mdx +22 -0
  414. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +39 -0
  415. package/templates/content/en/Event/ailk-launch-day-2026.mdx +35 -0
  416. package/templates/content/en/EventsList/events.mdx +26 -0
  417. package/templates/content/en/FAQPage/faq.mdx +420 -0
  418. package/templates/content/en/FAQPage/faq.schema.json +231 -0
  419. package/templates/content/en/GlossaryList/glossary.mdx +19 -0
  420. package/templates/content/en/GlossaryList/glossary.schema.json +300 -0
  421. package/templates/content/en/HomePage/home.mdx +312 -0
  422. package/templates/content/en/HomePage/home.schema.json +88 -0
  423. package/templates/content/en/HomePage/launch-service.mdx +475 -0
  424. package/templates/content/en/HomePage/launch-service.schema.json +138 -0
  425. package/templates/content/en/HowTo/add-mdx-content.mdx +38 -0
  426. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +35 -0
  427. package/templates/content/en/HowTo/set-up-json-ld.mdx +36 -0
  428. package/templates/content/en/Integration/github-actions.mdx +31 -0
  429. package/templates/content/en/Integration/openai-api.mdx +26 -0
  430. package/templates/content/en/Integration/vercel.mdx +32 -0
  431. package/templates/content/en/IntegrationsList/integrations.mdx +22 -0
  432. package/templates/content/en/JobPosting/content-strategist.mdx +46 -0
  433. package/templates/content/en/JobPosting/developer-advocate.mdx +47 -0
  434. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +57 -0
  435. package/templates/content/en/LandingPage/aeo-audit.mdx +19 -0
  436. package/templates/content/en/LandingPage/aeo-audit.schema.json +92 -0
  437. package/templates/content/en/LandingPage/agency-waitlist.mdx +340 -0
  438. package/templates/content/en/LandingPage/agency-waitlist.schema.json +152 -0
  439. package/templates/content/en/LandingPage/developers.mdx +19 -0
  440. package/templates/content/en/LandingPage/developers.schema.json +109 -0
  441. package/templates/content/en/LandingPage/for-agencies.mdx +390 -0
  442. package/templates/content/en/LandingPage/for-agencies.schema.json +107 -0
  443. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +417 -0
  444. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +105 -0
  445. package/templates/content/en/LandingPage/for-businesses.mdx +19 -0
  446. package/templates/content/en/LandingPage/for-businesses.schema.json +96 -0
  447. package/templates/content/en/LandingPage/platform.mdx +369 -0
  448. package/templates/content/en/LandingPage/platform.schema.json +166 -0
  449. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +19 -0
  450. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +102 -0
  451. package/templates/content/en/LandingPage/vs-wordpress.mdx +19 -0
  452. package/templates/content/en/LandingPage/vs-wordpress.schema.json +53 -0
  453. package/templates/content/en/NewsletterSignup/newsletter.mdx +236 -0
  454. package/templates/content/en/NewsletterSignup/newsletter.schema.json +91 -0
  455. package/templates/content/en/Organization/site-owner.mdx +16 -0
  456. package/templates/content/en/Partner/prisma.mdx +31 -0
  457. package/templates/content/en/Partner/supabase.mdx +30 -0
  458. package/templates/content/en/Partner/vercel.mdx +28 -0
  459. package/templates/content/en/PartnersList/partners.mdx +22 -0
  460. package/templates/content/en/Person/founder.mdx +18 -0
  461. package/templates/content/en/Pricing/pricing.mdx +538 -0
  462. package/templates/content/en/Pricing/pricing.schema.json +209 -0
  463. package/templates/content/en/PrivacyPage/privacy.mdx +165 -0
  464. package/templates/content/en/PrivacyPage/privacy.schema.json +64 -0
  465. package/templates/content/en/Product/ailk.mdx +19 -0
  466. package/templates/content/en/Product/ailk.schema.json +94 -0
  467. package/templates/content/en/Product/api-launch-kit.mdx +26 -0
  468. package/templates/content/en/Product/launch-kit.mdx +28 -0
  469. package/templates/content/en/ProductsList/products.mdx +26 -0
  470. package/templates/content/en/Project/ailk-phase-2.mdx +54 -0
  471. package/templates/content/en/ProjectsList/projects.mdx +25 -0
  472. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +27 -0
  473. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +28 -0
  474. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +39 -0
  475. package/templates/content/en/ResourcesHub/resources.mdx +22 -0
  476. package/templates/content/en/Service/consulting.mdx +22 -0
  477. package/templates/content/en/Service/implementation.mdx +30 -0
  478. package/templates/content/en/Service/launch-service.mdx +232 -0
  479. package/templates/content/en/Service/launch.mdx +228 -0
  480. package/templates/content/en/ServicesList/services.mdx +84 -0
  481. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +44 -0
  482. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +47 -0
  483. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +49 -0
  484. package/templates/content/en/TeamList/team.mdx +63 -0
  485. package/templates/content/en/TeamMember/founder.mdx +72 -0
  486. package/templates/content/en/TermsPage/terms.mdx +19 -0
  487. package/templates/content/en/TermsPage/terms.schema.json +99 -0
  488. package/templates/content/en/TestimonialsList/testimonials.mdx +44 -0
  489. package/templates/content/en/UseCasesList/use-cases.mdx +30 -0
  490. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +27 -0
  491. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +27 -0
  492. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +31 -0
  493. package/templates/content/en-XA/AboutPage/about.mdx +8 -0
  494. package/templates/content/en-XA/FAQPage/faq.mdx +8 -0
  495. package/templates/content/en-XA/HomePage/home.mdx +8 -0
  496. package/templates/content/es/_site.mdx +30 -0
  497. package/templates/content/fr/_site.mdx +30 -0
  498. package/templates/content/ja/_site.mdx +30 -0
  499. package/templates/content/pt-BR/AboutPage/about.mdx +8 -0
  500. package/templates/content/pt-BR/FAQPage/faq.mdx +8 -0
  501. package/templates/content/pt-BR/HomePage/home.mdx +23 -0
  502. package/templates/database/CHANGELOG.md +5 -0
  503. package/templates/database/README.md +234 -0
  504. package/templates/database/__tests__/user-cascade.test.ts +125 -0
  505. package/templates/database/auth/.gitkeep +0 -0
  506. package/templates/database/config/.gitkeep +0 -0
  507. package/templates/database/content/.gitkeep +0 -0
  508. package/templates/database/inbox/.gitkeep +0 -0
  509. package/templates/database/inbox/README.md +3 -0
  510. package/templates/database/inbox/__tests__/schema.test.ts +40 -0
  511. package/templates/database/inbox/schema.prisma +52 -0
  512. package/templates/database/jest.config.cjs +29 -0
  513. package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +328 -0
  514. package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +100 -0
  515. package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +28 -0
  516. package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +25 -0
  517. package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +42 -0
  518. package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +67 -0
  519. package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +21 -0
  520. package/templates/database/migrations/migration_lock.toml +3 -0
  521. package/templates/database/ops/.gitkeep +0 -0
  522. package/templates/database/package.json +45 -0
  523. package/templates/database/prisma.config.ts +20 -0
  524. package/templates/database/schema.prisma +487 -0
  525. package/templates/database/scripts/db-generate-locked.sh +80 -0
  526. package/templates/database/seed-test.ts +85 -0
  527. package/templates/database/tsconfig.build.json +16 -0
  528. package/templates/database/tsconfig.json +9 -0
  529. package/templates/package.json +125 -0
  530. package/templates/pnpm-workspace.yaml +6 -0
  531. package/templates/project.yaml +59 -0
  532. package/templates/tsconfig.base.json +18 -0
  533. package/templates/tsconfig.json +45 -0
@@ -0,0 +1,487 @@
1
+ // Customer Template Database Schema
2
+ //
3
+ // MINIMAL BUT EXTENSIBLE - Only stores auth and customer extensions
4
+ // All business data (leads, orders, analytics) lives on the Platform
5
+ //
6
+ // Tables (14 total — 11 auth/config + 3 inbox added in B1):
7
+ // - Better Auth (4): User, Session, Account, Verification (post-#587)
8
+ // - API Auth (2): ApiToken, RefreshToken
9
+ // - OAuth (2): OAuthClient, OAuthAuthorization
10
+ // - Push (1): PushToken
11
+ // - Extensions (2): UserData, SiteSetting
12
+ // - Inbox (3): Lead, Subscriber, Callback [namespace: database/inbox/]
13
+ //
14
+ // Aggregation strategy: Namespace-local schema files (e.g. database/inbox/schema.prisma)
15
+ // define models canonically for lint and documentation. Those same models are
16
+ // registered here so Prisma can generate from the single root schema.prisma.
17
+ // Phase 2 (ADR 0009 D4) will migrate to prismaSchemaFolder once multi-file
18
+ // generation is adopted repo-wide.
19
+
20
+ generator client {
21
+ provider = "prisma-client"
22
+ output = "./generated/client"
23
+ importFileExtension = "js"
24
+ }
25
+
26
+ datasource db {
27
+ provider = "postgresql"
28
+ }
29
+
30
+ // ============================================================================
31
+ // Better Auth Models (Web Dashboard Authentication — #587)
32
+ // ============================================================================
33
+
34
+ /// User - Dashboard users (site owners, admins, end-users)
35
+ ///
36
+ /// emailVerified is a Boolean per Better Auth's contract (NextAuth previously
37
+ /// stored the verification timestamp as DateTime?). The five custom relations
38
+ /// — ApiToken, RefreshToken, OAuthAuthorization, PushToken, UserData — keep
39
+ /// their onDelete: Cascade semantics so user removal cleanly tears down all
40
+ /// dependent rows.
41
+ model User {
42
+ id String @id @default(cuid())
43
+ email String @unique
44
+ name String?
45
+ image String?
46
+ emailVerified Boolean @default(false) @map("email_verified")
47
+
48
+ // Profile extensions
49
+ role String @default("user") // "user", "admin", "owner"
50
+
51
+ // Billing identity — provider-agnostic so Stripe is v1 value, not baked-in name.
52
+ // Webhook is the only writer (see apps/api/src/routes/webhooks/stripe.ts).
53
+ // #771 self-serve dashboard reads billingCustomerId for Stripe customer-portal URL.
54
+ billingProvider String? @map("billing_provider") // "stripe"; future: "lemonsqueezy", "paddle"
55
+ billingCustomerId String? @map("billing_customer_id") // cus_xxx (Stripe) or equivalent
56
+
57
+ // Subscription tier projection — derived from the Stripe webhook's product-aware
58
+ // routing (#898). Defaults to "free" until a customer.subscription.created event
59
+ // fires; flips to "pro" / "pro_plus" / etc. based on the line item's product.
60
+ // hasCarePlan is true only for pro_plus today; seatCount mirrors the line item
61
+ // quantity from Stripe's adjustable-quantity widget (1–50).
62
+ tier String @default("free") // "free" | "pro" | "pro_plus" | "enterprise" (retired "agency"/"commercial" rows migrated to "enterprise" — #3516)
63
+ hasCarePlan Boolean @default(false) @map("has_care_plan")
64
+ seatCount Int @default(1) @map("seat_count")
65
+
66
+ createdAt DateTime @default(now()) @map("created_at")
67
+ updatedAt DateTime @updatedAt @map("updated_at")
68
+
69
+ // Better Auth relations
70
+ sessions Session[]
71
+ accounts Account[]
72
+
73
+ // API & OAuth
74
+ apiTokens ApiToken[]
75
+ refreshTokens RefreshToken[]
76
+ oauthAuthorizations OAuthAuthorization[]
77
+ pushTokens PushToken[]
78
+
79
+ // Extensions
80
+ userData UserData[]
81
+
82
+ // Theme Wiring (TWP3-MAT #1359)
83
+ tenantTheme TenantTheme?
84
+ tenantSiteIdentity TenantSiteIdentity?
85
+ tenantSeo TenantSEO?
86
+
87
+ @@unique([billingProvider, billingCustomerId])
88
+ @@map("users")
89
+ }
90
+
91
+ /// Session - Active user sessions (Better Auth schema)
92
+ ///
93
+ /// Session.token is the opaque cookie value the dual-mode preHandler reads.
94
+ /// Deleting a row revokes the session; deleting the parent User cascades.
95
+ model Session {
96
+ id String @id
97
+ expiresAt DateTime @map("expires_at")
98
+ token String @unique
99
+ createdAt DateTime @default(now()) @map("created_at")
100
+ updatedAt DateTime @updatedAt @map("updated_at")
101
+ ipAddress String? @map("ip_address")
102
+ userAgent String? @map("user_agent")
103
+ userId String @map("user_id")
104
+
105
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
106
+
107
+ @@index([userId])
108
+ @@index([token])
109
+ @@map("sessions")
110
+ }
111
+
112
+ /// Account - OAuth provider connections (Google, GitHub, etc.)
113
+ ///
114
+ /// Better Auth's account shape: one row per (providerId, accountId) pair.
115
+ /// Cascade on User removal keeps OAuth links from orphaning.
116
+ model Account {
117
+ id String @id
118
+ accountId String @map("account_id")
119
+ providerId String @map("provider_id")
120
+ userId String @map("user_id")
121
+ accessToken String? @map("access_token") @db.Text
122
+ refreshToken String? @map("refresh_token") @db.Text
123
+ idToken String? @map("id_token") @db.Text
124
+ accessTokenExpiresAt DateTime? @map("access_token_expires_at")
125
+ refreshTokenExpiresAt DateTime? @map("refresh_token_expires_at")
126
+ scope String?
127
+ password String?
128
+ createdAt DateTime @default(now()) @map("created_at")
129
+ updatedAt DateTime @updatedAt @map("updated_at")
130
+
131
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
132
+
133
+ @@unique([providerId, accountId])
134
+ @@index([userId])
135
+ @@map("accounts")
136
+ }
137
+
138
+ /// Verification - Email / magic-link verification tokens (Better Auth schema)
139
+ model Verification {
140
+ id String @id
141
+ identifier String
142
+ value String
143
+ expiresAt DateTime @map("expires_at")
144
+ createdAt DateTime @default(now()) @map("created_at")
145
+ updatedAt DateTime @updatedAt @map("updated_at")
146
+
147
+ @@index([identifier])
148
+ @@map("verifications")
149
+ }
150
+
151
+ // ============================================================================
152
+ // API Token Authentication (Mobile Apps, CLI, External Clients)
153
+ // ============================================================================
154
+
155
+ /// ApiToken - Bearer tokens for non-web clients (mobile apps, API integrations)
156
+ model ApiToken {
157
+ id String @id @default(cuid())
158
+ userId String @map("user_id")
159
+
160
+ // Token identification
161
+ name String // "iPhone", "Android App", "API Integration"
162
+ token String @unique // Hashed token
163
+ tokenPrefix String @map("token_prefix") // First 8 chars for identification
164
+
165
+ // Client info
166
+ clientType String @default("api") @map("client_type") // "mobile", "api", "cli"
167
+ deviceInfo String? @map("device_info") // "iPhone 15 Pro", "Android 14"
168
+
169
+ // Permissions
170
+ scopes String[] @default([]) // ["read:profile", "write:data"]
171
+
172
+ // Lifecycle
173
+ expiresAt DateTime? @map("expires_at")
174
+ lastUsedAt DateTime? @map("last_used_at")
175
+ lastUsedIp String? @map("last_used_ip")
176
+ revokedAt DateTime? @map("revoked_at")
177
+
178
+ createdAt DateTime @default(now()) @map("created_at")
179
+
180
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
181
+ refreshTokens RefreshToken[]
182
+
183
+ @@index([userId])
184
+ @@index([token])
185
+ @@index([tokenPrefix])
186
+ @@map("api_tokens")
187
+ }
188
+
189
+ /// RefreshToken - Long-lived tokens for mobile session refresh
190
+ model RefreshToken {
191
+ id String @id @default(cuid())
192
+ userId String @map("user_id")
193
+ apiTokenId String @map("api_token_id")
194
+
195
+ token String @unique // Hashed
196
+
197
+ // Lifecycle
198
+ expiresAt DateTime @map("expires_at")
199
+ usedAt DateTime? @map("used_at") // Last time used to get new access token
200
+ revokedAt DateTime? @map("revoked_at")
201
+
202
+ createdAt DateTime @default(now()) @map("created_at")
203
+
204
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
205
+ apiToken ApiToken @relation(fields: [apiTokenId], references: [id], onDelete: Cascade)
206
+
207
+ @@index([userId])
208
+ @@index([apiTokenId])
209
+ @@index([token])
210
+ @@map("refresh_tokens")
211
+ }
212
+
213
+ // ============================================================================
214
+ // OAuth 2.0 (ChatGPT Plugin, Slack Bot, External Integrations)
215
+ // ============================================================================
216
+
217
+ /// OAuthClient - Registered OAuth applications (ChatGPT, Slack, etc.)
218
+ model OAuthClient {
219
+ id String @id @default(cuid())
220
+
221
+ // Client identification
222
+ name String // "ChatGPT Plugin", "Slack Bot"
223
+ description String?
224
+ logoUrl String? @map("logo_url")
225
+
226
+ // OAuth credentials
227
+ clientId String @unique @map("client_id") // Public identifier
228
+ clientSecret String @map("client_secret") // Hashed secret
229
+
230
+ // OAuth config
231
+ redirectUris String[] @map("redirect_uris") // Allowed callback URLs
232
+ scopes String[] @default([]) // Allowed scopes
233
+
234
+ // Settings
235
+ enabled Boolean @default(true)
236
+ trusted Boolean @default(false) // Skip consent screen
237
+
238
+ createdAt DateTime @default(now()) @map("created_at")
239
+ updatedAt DateTime @updatedAt @map("updated_at")
240
+
241
+ authorizations OAuthAuthorization[]
242
+
243
+ @@map("oauth_clients")
244
+ }
245
+
246
+ /// OAuthAuthorization - User grants to OAuth clients
247
+ model OAuthAuthorization {
248
+ id String @id @default(cuid())
249
+ userId String @map("user_id")
250
+ clientId String @map("client_id")
251
+
252
+ // Granted scopes (may be subset of client's allowed scopes)
253
+ scopes String[]
254
+
255
+ // Tokens
256
+ accessToken String @unique @map("access_token") // Hashed
257
+ refreshToken String? @unique @map("refresh_token") // Hashed
258
+
259
+ // Lifecycle
260
+ expiresAt DateTime @map("expires_at")
261
+ revokedAt DateTime? @map("revoked_at")
262
+
263
+ createdAt DateTime @default(now()) @map("created_at")
264
+ updatedAt DateTime @updatedAt @map("updated_at")
265
+
266
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
267
+ client OAuthClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
268
+
269
+ @@unique([userId, clientId])
270
+ @@index([userId])
271
+ @@index([clientId])
272
+ @@index([accessToken])
273
+ @@map("oauth_authorizations")
274
+ }
275
+
276
+ // ============================================================================
277
+ // Push Notifications (Mobile Apps, Web Push)
278
+ // ============================================================================
279
+
280
+ /// PushToken - Device tokens for push notifications
281
+ model PushToken {
282
+ id String @id @default(cuid())
283
+ userId String @map("user_id")
284
+
285
+ // Token details
286
+ token String @unique // FCM or APNs token
287
+ platform String // "ios", "android", "web"
288
+
289
+ // Device info
290
+ deviceId String? @map("device_id") // Unique device identifier
291
+ deviceName String? @map("device_name") // "iPhone 15 Pro"
292
+ appVersion String? @map("app_version") // "1.2.3"
293
+
294
+ // Status
295
+ enabled Boolean @default(true)
296
+ failedAt DateTime? @map("failed_at") // Last delivery failure
297
+ failCount Int @default(0) @map("fail_count")
298
+
299
+ createdAt DateTime @default(now()) @map("created_at")
300
+ updatedAt DateTime @updatedAt @map("updated_at")
301
+
302
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
303
+
304
+ @@index([userId])
305
+ @@index([platform])
306
+ @@map("push_tokens")
307
+ }
308
+
309
+ // ============================================================================
310
+ // User Data Extensions (Customer Customizations)
311
+ // ============================================================================
312
+
313
+ /// UserData - Flexible key-value storage for customer extensions
314
+ /// Use this for: preferences, saved items, custom fields, feature flags
315
+ model UserData {
316
+ id String @id @default(cuid())
317
+ userId String @map("user_id")
318
+
319
+ // Key-value storage
320
+ key String // "preferences", "saved_products", "notification_settings"
321
+ value Json // Flexible JSON storage
322
+
323
+ createdAt DateTime @default(now()) @map("created_at")
324
+ updatedAt DateTime @updatedAt @map("updated_at")
325
+
326
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
327
+
328
+ @@unique([userId, key])
329
+ @@index([userId])
330
+ @@index([key])
331
+ @@map("user_data")
332
+ }
333
+
334
+ // ============================================================================
335
+ // Site Settings (Site-wide Configuration)
336
+ // ============================================================================
337
+
338
+ /// SiteSetting - Key-value settings for the customer site
339
+ /// Use for: feature flags, custom config, cached Platform data
340
+ model SiteSetting {
341
+ id String @id @default(cuid())
342
+ key String @unique // "feature_flags", "cached_config", "custom_theme"
343
+ value Json
344
+
345
+ createdAt DateTime @default(now()) @map("created_at")
346
+ updatedAt DateTime @updatedAt @map("updated_at")
347
+
348
+ @@map("site_settings")
349
+ }
350
+
351
+ // ============================================================================
352
+ // Inbox Namespace (database/inbox/schema.prisma — B1)
353
+ //
354
+ // Anonymous lead/subscriber/callback data. siteId is a plain String — no FK
355
+ // to auth-namespace models per ADR 0009 D2. Cross-namespace assembly routes
356
+ // through apps/api/src/services/ (D3).
357
+ // ============================================================================
358
+
359
+ /// Lead - anonymous lead submission (Phase B submit_lead tool / B6 route)
360
+ model Lead {
361
+ id String @id @default(cuid())
362
+ siteId String @map("site_id")
363
+ source String
364
+ email String
365
+ payload Json
366
+ createdAt DateTime @default(now()) @map("created_at")
367
+
368
+ @@index([siteId])
369
+ @@index([createdAt])
370
+ @@map("leads")
371
+ }
372
+
373
+ /// Subscriber - newsletter subscription (Phase B subscribe_newsletter tool / B6 route)
374
+ model Subscriber {
375
+ id String @id @default(cuid())
376
+ siteId String @map("site_id")
377
+ email String
378
+ source String
379
+ createdAt DateTime @default(now()) @map("created_at")
380
+
381
+ @@unique([siteId, email])
382
+ @@index([siteId])
383
+ @@map("subscribers")
384
+ }
385
+
386
+ /// Callback - callback request (Phase B request_callback tool / B6 route)
387
+ model Callback {
388
+ id String @id @default(cuid())
389
+ siteId String @map("site_id")
390
+ name String
391
+ phone String
392
+ preferredTime String? @map("preferred_time")
393
+ payload Json
394
+ createdAt DateTime @default(now()) @map("created_at")
395
+
396
+ @@index([siteId])
397
+ @@index([createdAt])
398
+ @@map("callbacks")
399
+ }
400
+
401
+ // ============================================================================
402
+ // License Telemetry (apps-paid/license-issuer — S2 #1031)
403
+ //
404
+ // Time-series records of opt-in seat-usage telemetry from customer deployments.
405
+ // Written by POST /telemetry on the license-issuer when AILK_TELEMETRY=1.
406
+ // Enables queries computing the distribution of active_user_count / seat_count
407
+ // across the customer base over a time window (S2 trigger condition monitoring).
408
+ //
409
+ // No PII beyond customer_id (Stripe customer ID — pseudo-anonymous).
410
+ // ============================================================================
411
+
412
+ /// TelemetryRecord - opt-in seat-usage telemetry from customer deployments.
413
+ ///
414
+ /// timestamp is the client-side observation time (from the telemetry payload).
415
+ /// recordedAt is the server-side receipt time (default now()).
416
+ /// The (customerId, recordedAt DESC) index supports "latest record per customer"
417
+ /// queries; the recordedAt DESC index supports time-window aggregate queries.
418
+ model TelemetryRecord {
419
+ id String @id @default(cuid())
420
+ customerId String @map("customer_id")
421
+ activeUserCount Int @map("active_user_count")
422
+ seatCount Int @map("seat_count")
423
+ timestamp DateTime
424
+ recordedAt DateTime @default(now()) @map("recorded_at")
425
+
426
+ @@index([customerId, recordedAt(sort: Desc)])
427
+ @@index([recordedAt(sort: Desc)])
428
+ @@map("telemetry_records")
429
+ }
430
+
431
+ // ============================================================================
432
+ // Theme Wiring — Per-tenant design system persistence (TWP3-MAT #1359)
433
+ //
434
+ // Three tables, one per @working-theory/site-config schema. Each row stores the
435
+ // Zod-validated JSON blob for a single tenant (identified by userId from the
436
+ // Better Auth session). Validation is enforced at the API layer; the DB stores
437
+ // the already-validated blob in a Json column.
438
+ //
439
+ // Design decisions (spec §3):
440
+ // - One table per schema (not a polymorphic config table) — different update
441
+ // cadences + consumer access patterns.
442
+ // - Json column for the blob — schema evolves without DB migration on change;
443
+ // Zod validates at API boundary.
444
+ // - userId is the Better Auth User id — plain String FK; onDelete Cascade
445
+ // ensures rows are removed when the user account is deleted.
446
+ // - @@unique([userId]) enforces one active record per tenant per config type
447
+ // (upsert semantics at the API layer).
448
+ // ============================================================================
449
+
450
+ /// TenantTheme — per-tenant ThemeSchema blob (TWP3-MAT #1359)
451
+ model TenantTheme {
452
+ id String @id @default(cuid())
453
+ userId String @unique @map("user_id")
454
+ data Json
455
+ createdAt DateTime @default(now()) @map("created_at")
456
+ updatedAt DateTime @updatedAt @map("updated_at")
457
+
458
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
459
+
460
+ @@map("tenant_themes")
461
+ }
462
+
463
+ /// TenantSiteIdentity — per-tenant SiteIdentitySchema blob (TWP3-MAT #1359)
464
+ model TenantSiteIdentity {
465
+ id String @id @default(cuid())
466
+ userId String @unique @map("user_id")
467
+ data Json
468
+ createdAt DateTime @default(now()) @map("created_at")
469
+ updatedAt DateTime @updatedAt @map("updated_at")
470
+
471
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
472
+
473
+ @@map("tenant_site_identities")
474
+ }
475
+
476
+ /// TenantSEO — per-tenant SEOSchema blob (TWP3-MAT #1359)
477
+ model TenantSEO {
478
+ id String @id @default(cuid())
479
+ userId String @unique @map("user_id")
480
+ data Json
481
+ createdAt DateTime @default(now()) @map("created_at")
482
+ updatedAt DateTime @updatedAt @map("updated_at")
483
+
484
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
485
+
486
+ @@map("tenant_seos")
487
+ }
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # db-generate-locked.sh — serialise concurrent `pnpm --filter @working-theory/database db:generate`
3
+ # calls so parallel prebuild scripts don't race on database/generated/client/.
4
+ #
5
+ # Background: four apps-paid/* packages each declare a `prebuild` that calls
6
+ # db:generate. When `pnpm -r build` triggers them in parallel the concurrent
7
+ # Prisma invocations race on the shared database/generated/client/ output dir,
8
+ # producing either "directory not empty" rmdir errors or "doesn't look like a
9
+ # generated Prisma Client" sanity-check failures. See issue #1690.
10
+ #
11
+ # Lock mechanism: mkdir(1) is atomic on POSIX filesystems (including macOS APFS).
12
+ # The first caller wins the lock dir and runs the generate; concurrent callers
13
+ # spin-wait. If the winner finishes before they time out, they see the output
14
+ # dir already present and skip. Stale locks are detected via a PID file stored
15
+ # inside the lock dir; if the owning PID is no longer alive the lock is removed
16
+ # and the next caller re-runs the generate.
17
+
18
+ set -euo pipefail
19
+
20
+ # Workspace root: two levels up from database/scripts/
21
+ WORKSPACE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
22
+ DATABASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23
+
24
+ LOCK_DIR="$WORKSPACE_ROOT/.claude/.db-generate.lock"
25
+ TIMEOUT_SECONDS=120
26
+ POLL_INTERVAL=1
27
+
28
+ # --- Acquire lock via atomic mkdir ---
29
+ acquire_lock() {
30
+ if mkdir "$LOCK_DIR" 2>/dev/null; then
31
+ echo "$$" > "$LOCK_DIR/pid"
32
+ return 0 # acquired
33
+ fi
34
+ return 1 # not acquired
35
+ }
36
+
37
+ # --- Check if a stale lock can be cleared ---
38
+ maybe_clear_stale_lock() {
39
+ local pid_file="$LOCK_DIR/pid"
40
+ if [ -f "$pid_file" ]; then
41
+ local owner_pid
42
+ owner_pid="$(cat "$pid_file" 2>/dev/null || echo "")"
43
+ if [ -n "$owner_pid" ] && ! kill -0 "$owner_pid" 2>/dev/null; then
44
+ # Owner process is gone — lock is stale; remove it
45
+ rm -rf "$LOCK_DIR"
46
+ fi
47
+ fi
48
+ }
49
+
50
+ # --- Release lock on exit ---
51
+ release_lock() {
52
+ rm -rf "$LOCK_DIR" 2>/dev/null || true
53
+ }
54
+
55
+ # --- Main ---
56
+ elapsed=0
57
+
58
+ while true; do
59
+ maybe_clear_stale_lock
60
+ if acquire_lock; then
61
+ trap release_lock EXIT
62
+ break
63
+ fi
64
+
65
+ # Another process holds the lock — if it already wrote the output, skip
66
+ if [ -d "$DATABASE_DIR/generated/client" ]; then
67
+ echo "db-generate-locked: client already present (written by concurrent process), skipping."
68
+ exit 0
69
+ fi
70
+
71
+ elapsed=$((elapsed + POLL_INTERVAL))
72
+ if [ "$elapsed" -ge "$TIMEOUT_SECONDS" ]; then
73
+ echo "db-generate-locked: timed out waiting for lock after ${TIMEOUT_SECONDS}s; running generate anyway." >&2
74
+ break
75
+ fi
76
+ sleep "$POLL_INTERVAL"
77
+ done
78
+
79
+ echo "db-generate-locked: running db:generate for @working-theory/database"
80
+ pnpm --filter @working-theory/database db:generate
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Test-user seed for the pre-OSS e2e seam (#517 — §5.1).
3
+ *
4
+ * Seeds three Better Auth User rows distinguished by the `role` field
5
+ * (`user`, `admin`, `owner` — matching the inline comment in schema.prisma).
6
+ * Pair with the triple-gated /api/test/auth/session route to mint sessions
7
+ * for these users from a CI e2e harness without going through OAuth.
8
+ *
9
+ * GUARD — this script REFUSES to run unless AILK_TEST_AUTH_ENABLED === 'true'.
10
+ * A production deployment with this script accidentally invoked but the gate
11
+ * unset will exit non-zero with a loud error rather than silently seeding
12
+ * test-user rows into a real customer DB.
13
+ *
14
+ * Re-run safe — uses `upsert` keyed by email so re-running the seed is
15
+ * idempotent. Will NOT overwrite name or role on subsequent runs (the
16
+ * `update: {}` clause leaves existing rows untouched).
17
+ *
18
+ * Run via:
19
+ * pnpm --filter @working-theory/database db:seed:test
20
+ *
21
+ * Environment variables read:
22
+ * AILK_TEST_AUTH_ENABLED — must be the literal string "true" or this
23
+ * script exits 1 without writing anything.
24
+ * DATABASE_URL — required (the Prisma adapter uses it).
25
+ */
26
+ import "dotenv/config";
27
+
28
+ import { PrismaClient } from "./generated/client/client.js";
29
+
30
+ const TEST_USERS = [
31
+ {
32
+ id: "test-e2e-user",
33
+ email: "test:e2e-user@ailaunchkit.test",
34
+ name: "E2E Test User",
35
+ role: "user",
36
+ },
37
+ {
38
+ id: "test-e2e-admin",
39
+ email: "test:e2e-admin@ailaunchkit.test",
40
+ name: "E2E Test Admin",
41
+ role: "admin",
42
+ },
43
+ {
44
+ id: "test-e2e-owner",
45
+ email: "test:e2e-owner@ailaunchkit.test",
46
+ name: "E2E Test Owner",
47
+ role: "owner",
48
+ },
49
+ ] as const;
50
+
51
+ async function main(): Promise<void> {
52
+ if (process.env.AILK_TEST_AUTH_ENABLED !== "true") {
53
+ console.error(
54
+ "[seed-test] REFUSING to run: AILK_TEST_AUTH_ENABLED must be exactly 'true'. " +
55
+ "This guard prevents accidental seeding of test users into a production DB.",
56
+ );
57
+ process.exit(1);
58
+ }
59
+
60
+ const prisma = new PrismaClient();
61
+ try {
62
+ for (const user of TEST_USERS) {
63
+ await prisma.user.upsert({
64
+ where: { email: user.email },
65
+ create: {
66
+ id: user.id,
67
+ email: user.email,
68
+ name: user.name,
69
+ emailVerified: true,
70
+ role: user.role,
71
+ },
72
+ update: {},
73
+ });
74
+
75
+ console.log(`[seed-test] upserted ${user.email} (role=${user.role})`);
76
+ }
77
+ } finally {
78
+ await prisma.$disconnect();
79
+ }
80
+ }
81
+
82
+ main().catch((err) => {
83
+ console.error("[seed-test] failed:", err);
84
+ process.exit(1);
85
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./generated",
8
+ "declaration": true,
9
+ "composite": true,
10
+ "noEmit": false,
11
+ "skipLibCheck": true,
12
+ "types": ["node"]
13
+ },
14
+ "include": ["generated/**/*.ts"],
15
+ "exclude": ["node_modules", "dist", "__tests__"]
16
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "rootDir": ".",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["prisma.config.ts", "seed-test.ts"]
9
+ }