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,135 @@
1
+ /**
2
+ * Universal OG image route — /og?title=...&schemaType=...
3
+ *
4
+ * Design decisions (per spec § 3):
5
+ * - next/og ImageResponse at 1200×630 (Open Graph standard)
6
+ * - System sans-serif only — no .ttf fetches (the #1 source of OG bugs)
7
+ * - force-static: Vercel caches generated images at the edge after first request
8
+ * - Fallback strategy: on any ImageResponse throw, return a 302 redirect to
9
+ * /og-default.png (the static PNG in apps/web/public/). The redirect is
10
+ * edge-compatible (no fs access needed) and exposing the fallback URL is
11
+ * acceptable per spec § 3 ("redirecting is simpler but exposes the fallback URL;
12
+ * both acceptable"). Chosen over stream-bytes because edge runtime has no fs.
13
+ *
14
+ * Sanitization:
15
+ * - Strip control characters \x00–\x1F and \x7F from title and schemaType
16
+ * - Cap title at 200 chars, schemaType at 50 chars
17
+ * - If sanitization yields an empty title, fall back to static PNG redirect
18
+ */
19
+ import { clientError } from "@working-theory/page-renderer/edge";
20
+ import { tokens } from "@working-theory/theme";
21
+ import { ImageResponse } from "next/og";
22
+
23
+ export const runtime = "edge";
24
+ export const dynamic = "force-static";
25
+
26
+ // Strips C0 control characters (U+0000–U+001F) and DEL (U+007F).
27
+ // eslint-disable-next-line no-control-regex
28
+ const CONTROL_CHAR_RE = new RegExp("[\u0000-\u001F\u007F]", "g");
29
+
30
+ const SYSTEM_FONT = "system-ui, -apple-system, sans-serif";
31
+
32
+ function sanitize(raw: string, cap: number): string {
33
+ return raw.replace(CONTROL_CHAR_RE, "").slice(0, cap);
34
+ }
35
+
36
+ function serveFallback(): Response {
37
+ // 302 redirect to the static PNG served by Next.js from /public.
38
+ // Edge-compatible: no fs access required.
39
+ // Using new Response + Location header instead of Response.redirect() because
40
+ // Response.redirect() requires an absolute URL in the Fetch API spec, but
41
+ // relative redirects work correctly in Next.js edge middleware at runtime.
42
+ return new Response(null, {
43
+ status: 302,
44
+ headers: { Location: "/og-default.png" },
45
+ });
46
+ }
47
+
48
+ export async function GET(request: Request): Promise<Response> {
49
+ const { searchParams } = new URL(request.url);
50
+
51
+ const rawTitle = searchParams.get("title") ?? "";
52
+ const rawSchemaType = searchParams.get("schemaType") ?? "";
53
+
54
+ const siteName = process.env.NEXT_PUBLIC_SITE_NAME ?? "AI Launch Kit";
55
+ const wordmark = process.env.NEXT_PUBLIC_SITE_WORDMARK ?? "ailaunchkit.ai";
56
+
57
+ const title = sanitize(rawTitle || siteName, 200);
58
+ const schemaType = rawSchemaType ? sanitize(rawSchemaType, 50) : "";
59
+
60
+ // Post-sanitization empty title → fallback
61
+ if (!title) {
62
+ return serveFallback();
63
+ }
64
+
65
+ try {
66
+ return new ImageResponse(
67
+ <div
68
+ style={{
69
+ display: "flex",
70
+ flexDirection: "column",
71
+ justifyContent: "center",
72
+ alignItems: "flex-start",
73
+ width: "100%",
74
+ height: "100%",
75
+ backgroundColor: tokens.colors.light.brand,
76
+ padding: "80px",
77
+ fontFamily: SYSTEM_FONT,
78
+ position: "relative",
79
+ }}
80
+ >
81
+ {schemaType ? (
82
+ <div
83
+ style={{
84
+ position: "absolute",
85
+ top: "48px",
86
+ right: "48px",
87
+ backgroundColor: "rgba(0,0,0,0.25)",
88
+ color: tokens.colors.light.fg,
89
+ borderRadius: "9999px",
90
+ padding: "6px 18px",
91
+ fontSize: "18px",
92
+ fontFamily: SYSTEM_FONT,
93
+ }}
94
+ >
95
+ {schemaType}
96
+ </div>
97
+ ) : null}
98
+
99
+ <div
100
+ style={{
101
+ color: tokens.colors.light.fg,
102
+ fontSize: "56px",
103
+ fontWeight: 700,
104
+ lineHeight: 1.1,
105
+ maxWidth: "880px",
106
+ fontFamily: SYSTEM_FONT,
107
+ }}
108
+ >
109
+ {title}
110
+ </div>
111
+
112
+ <div
113
+ style={{
114
+ position: "absolute",
115
+ bottom: "48px",
116
+ right: "48px",
117
+ color: tokens.colors.light.fg,
118
+ fontSize: "24px",
119
+ opacity: 0.75,
120
+ fontFamily: SYSTEM_FONT,
121
+ }}
122
+ >
123
+ {wordmark}
124
+ </div>
125
+ </div>,
126
+ {
127
+ width: 1200,
128
+ height: 630,
129
+ },
130
+ );
131
+ } catch (err) {
132
+ clientError(err);
133
+ return serveFallback();
134
+ }
135
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * GET /robots.txt
3
+ *
4
+ * Returns a robots.txt with sensible defaults for an AX-first product:
5
+ * - All crawlers allowed by default
6
+ * - Sitemap location declared
7
+ * - Optional path disallow via AILK_ROBOTS_DISALLOW env var
8
+ * (comma-separated paths, e.g. "/admin/,/private/")
9
+ */
10
+
11
+ import { resolveBaseUrl } from "../../src/lib/routes";
12
+
13
+ export function GET(): Response {
14
+ const baseUrl = resolveBaseUrl();
15
+ const disallowEnv = process.env["AILK_ROBOTS_DISALLOW"] ?? "";
16
+
17
+ const disallowPaths = disallowEnv
18
+ .split(",")
19
+ .map((p) => p.trim())
20
+ .filter((p) => p.length > 0);
21
+
22
+ const lines = [
23
+ "User-agent: *",
24
+ ...disallowPaths.map((p) => `Disallow: ${p}`),
25
+ "",
26
+ `Sitemap: ${baseUrl}/sitemap.xml`,
27
+ ];
28
+
29
+ return new Response(lines.join("\n"), {
30
+ status: 200,
31
+ headers: {
32
+ "content-type": "text/plain; charset=utf-8",
33
+ "cache-control": "public, max-age=86400",
34
+ },
35
+ });
36
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * GET /sitemap.xml
3
+ *
4
+ * Returns a standard XML sitemap covering all content seeds in content/.
5
+ * Crawlers (Google, Bing) and AI agents use this to discover pages.
6
+ *
7
+ * Includes per-locale <xhtml:link rel="alternate"> entries per Google's
8
+ * sitemap protocol for international content. Locale list is read from
9
+ * NEXT_PUBLIC_ENABLED_LOCALES (baked by next.config.mjs from project.yaml
10
+ * at build time) — same pattern as middleware.ts. This avoids importing
11
+ * node:fs-dependent lib/locales into the prerender context where cwd is
12
+ * apps/web/ but project.yaml lives at the repo root.
13
+ *
14
+ * Phase 3 S3: pages are listed per enabled locale via the adapter's
15
+ * locale-scoped `listPages(type, locale)`. Lists are scoped — see the
16
+ * adapter contract in @working-theory/content-adapters/src/mdx/adapter.ts — so a
17
+ * page must exist in each locale's content directory to be enumerated for
18
+ * that locale. The default-locale list still picks up legacy non-prefixed
19
+ * content via the adapter's legacy fallback.
20
+ *
21
+ * URL composition:
22
+ * - Default locale: bare path /<path> (matches next-intl as-needed serving)
23
+ * - Non-default locales: /<locale>/<path>
24
+ *
25
+ * lastmod uses the page's publishedAt frontmatter field when present;
26
+ * falls back to the current date so the sitemap is always valid.
27
+ *
28
+ * DI note: `createSitemapHandler` accepts an optional `getContentAdapter` dep
29
+ * for testing — tests inject a stub adapter to avoid mocking the internal module.
30
+ * The exported `GET` uses the real implementation.
31
+ */
32
+
33
+ import { SCHEMA_ORG_TYPES } from "@working-theory/content-adapters";
34
+ import type { ContentAdapter, SchemaOrgType } from "@working-theory/content-adapters";
35
+ import { getContentAdapter as defaultGetContentAdapter } from "@working-theory/page-renderer";
36
+
37
+ import { resolveBaseUrl, slugToPath } from "../../src/lib/routes";
38
+
39
+ export interface SitemapHandlerDeps {
40
+ /**
41
+ * Content adapter factory. Defaults to the real implementation.
42
+ * Override in tests to inject a stub adapter without mocking the internal module.
43
+ */
44
+ getContentAdapter?: () => ContentAdapter;
45
+ }
46
+
47
+ function escapeXml(value: string): string {
48
+ return value
49
+ .replace(/&/g, "&amp;")
50
+ .replace(/</g, "&lt;")
51
+ .replace(/>/g, "&gt;")
52
+ .replace(/"/g, "&quot;")
53
+ .replace(/'/g, "&apos;");
54
+ }
55
+
56
+ /**
57
+ * Builds the locale-prefixed URL for a given locale and path.
58
+ * Default locale uses bare path; non-default uses /<locale>/<path>.
59
+ */
60
+ function buildLocaleUrl(
61
+ baseUrl: string,
62
+ locale: string,
63
+ defaultLocale: string,
64
+ path: string,
65
+ ): string {
66
+ if (locale === defaultLocale) {
67
+ return `${baseUrl}${path}`;
68
+ }
69
+ // Avoid double-slash: path starts with "/", locale prefix ends without one.
70
+ return `${baseUrl}/${locale}${path}`;
71
+ }
72
+
73
+ /**
74
+ * Build a sitemap GET handler parameterised by deps.
75
+ * Tests inject a stub `getContentAdapter` to avoid mocking the internal module.
76
+ */
77
+ export function createSitemapHandler(deps: SitemapHandlerDeps = {}) {
78
+ const resolveAdapter = deps.getContentAdapter ?? defaultGetContentAdapter;
79
+ return async function sitemapGET(): Promise<Response> {
80
+ const adapter = resolveAdapter();
81
+ const baseUrl = resolveBaseUrl();
82
+ const today = new Date().toISOString().slice(0, 10);
83
+ // Locale list and default locale are baked at build time by next.config.mjs
84
+ // from project.yaml. Reading env vars here mirrors middleware.ts exactly —
85
+ // avoids node:fs in prerender context where cwd != repo root.
86
+ const locales = (process.env.NEXT_PUBLIC_ENABLED_LOCALES ?? "en").split(",");
87
+ const defaultLocale = process.env.NEXT_PUBLIC_DEFAULT_LOCALE ?? "en";
88
+
89
+ const urlEntries: string[] = [];
90
+
91
+ const types = SCHEMA_ORG_TYPES as readonly SchemaOrgType[];
92
+
93
+ // Build (locale, type) tuples and fetch all lists in parallel. Adapter
94
+ // list calls are locale-scoped (no cross-locale fallback), so each locale
95
+ // gets its own enumeration — a page that exists only in `en/` is NOT
96
+ // auto-emitted for `es/` URLs. This honors AC-i18n-08 (lists scoped).
97
+ const tuples = locales.flatMap((locale) =>
98
+ types.map((type) => ({ locale, type })),
99
+ );
100
+ const lists = await Promise.all(
101
+ tuples.map(({ type, locale }) => adapter.listPages(type, locale)),
102
+ );
103
+
104
+ for (let i = 0; i < tuples.length; i++) {
105
+ const { locale } = tuples[i]!;
106
+ for (const page of lists[i]!) {
107
+ const path = slugToPath(page.type, page.slug);
108
+ const lastmod = page.publishedAt ?? today;
109
+
110
+ const defaultUrl = `${baseUrl}${path}`;
111
+ const localeUrl = buildLocaleUrl(baseUrl, locale, defaultLocale, path);
112
+ const loc = escapeXml(localeUrl);
113
+
114
+ const alternates = locales
115
+ .map(
116
+ (alt) =>
117
+ ` <xhtml:link rel="alternate" hreflang="${escapeXml(alt)}" href="${escapeXml(buildLocaleUrl(baseUrl, alt, defaultLocale, path))}"/>`,
118
+ )
119
+ .join("\n");
120
+
121
+ const xDefault = ` <xhtml:link rel="alternate" hreflang="x-default" href="${escapeXml(defaultUrl)}"/>`;
122
+
123
+ urlEntries.push(
124
+ ` <url>\n <loc>${loc}</loc>\n <lastmod>${lastmod}</lastmod>\n${alternates}\n${xDefault}\n </url>`,
125
+ );
126
+ }
127
+ }
128
+
129
+ const xml = [
130
+ '<?xml version="1.0" encoding="UTF-8"?>',
131
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">',
132
+ ...urlEntries,
133
+ "</urlset>",
134
+ ].join("\n");
135
+
136
+ return new Response(xml, {
137
+ status: 200,
138
+ headers: {
139
+ "content-type": "application/xml; charset=utf-8",
140
+ "cache-control": "public, max-age=3600",
141
+ },
142
+ });
143
+ };
144
+ }
145
+
146
+ export const GET = createSitemapHandler();
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Better Auth initialisation — Node.js runtime only.
3
+ *
4
+ * Wires Better Auth's Prisma adapter (DB session strategy) against the
5
+ * `prismaForAuth` singleton in `./lib/prisma-for-auth.ts`. The adapter mediates
6
+ * all DB access so apps/web never imports @working-theory/database elsewhere.
7
+ *
8
+ * Exports:
9
+ * createAuth — factory that accepts optional `prisma` dep for testing.
10
+ * auth — lazy-proxy singleton using the real `prismaForAuth` client.
11
+ * `auth.handler` is the Web Fetch handler consumed by
12
+ * app/api/auth/[...all]/route.ts (via toNextJsHandler).
13
+ * `auth.api.getSession({ headers })` reads the active session
14
+ * server-side.
15
+ *
16
+ * Provider activation follows env-var presence (see lib/auth-env.ts) so the
17
+ * OSS standalone default — no env vars set — boots cleanly with zero providers.
18
+ *
19
+ * DI note: `createAuth` accepts an optional `prisma` client for testing —
20
+ * tests can pass a lightweight stub to avoid a real DB connection. The
21
+ * exported `auth` singleton uses the real `prismaForAuth` client.
22
+ * Pattern mirrors `apps-paid/dashboard-web/auth.ts`.
23
+ *
24
+ * Replaces the NextAuth v5-beta wiring per spec
25
+ * docs/internal/specs/ailk-pre-oss-security-phase-0.5-s0.5-better-auth-migration-spec.md.
26
+ */
27
+
28
+ import type { PrismaClient } from "@working-theory/database";
29
+ import { betterAuth } from "better-auth";
30
+ import { prismaAdapter } from "better-auth/adapters/prisma";
31
+ import { nextCookies } from "better-auth/next-js";
32
+
33
+ import { getActiveProviderIds } from "./lib/auth-env";
34
+
35
+ // Lazy-load prisma-for-auth: import is deferred until createAuth() is called
36
+ // without an explicit `prisma` dep. This prevents the PrismaClient singleton
37
+ // from initialising (and calling @working-theory/database) at module-load time, which
38
+ // lets tests import `createAuth` without triggering a DB connection.
39
+ function getDefaultPrisma(): PrismaClient {
40
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
41
+ const { prismaForAuth } = require("./lib/prisma-for-auth") as {
42
+ prismaForAuth: PrismaClient;
43
+ };
44
+ return prismaForAuth;
45
+ }
46
+
47
+ type SocialProviders = {
48
+ google?: { clientId: string; clientSecret: string };
49
+ github?: { clientId: string; clientSecret: string };
50
+ };
51
+
52
+ function buildSocialProviders(): SocialProviders {
53
+ const activeIds = getActiveProviderIds();
54
+ const providers: SocialProviders = {};
55
+
56
+ if (activeIds.includes("google")) {
57
+ providers.google = {
58
+ clientId: process.env.GOOGLE_CLIENT_ID!,
59
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
60
+ };
61
+ }
62
+
63
+ if (activeIds.includes("github")) {
64
+ providers.github = {
65
+ clientId: process.env.GITHUB_CLIENT_ID!,
66
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
67
+ };
68
+ }
69
+
70
+ // "email" is reported by getActiveProviderIds when AUTH_EMAIL_SERVER +
71
+ // AUTH_EMAIL_FROM are set, but Better Auth core does not ship a built-in
72
+ // magic-link provider — that wiring is deferred to a follow-up
73
+ // (see spec §7 "Out of Scope").
74
+
75
+ return providers;
76
+ }
77
+
78
+ /**
79
+ * Resolve the secret used to sign session cookies. Better Auth requires a
80
+ * secret in production. In dev / test the bare default keeps the app bootable
81
+ * without configuration; production deployments MUST set BETTER_AUTH_SECRET.
82
+ */
83
+ function resolveSecret(): string {
84
+ return (
85
+ process.env.BETTER_AUTH_SECRET ??
86
+ "ailk-dev-secret-do-not-use-in-production"
87
+ );
88
+ }
89
+
90
+ export interface CreateAuthDeps {
91
+ /**
92
+ * Prisma client for the Better Auth adapter.
93
+ * Defaults to the singleton from `lib/prisma-for-auth.ts`.
94
+ * Override in tests to pass a lightweight stub and avoid a real DB
95
+ * connection at module-load time.
96
+ */
97
+ prisma?: PrismaClient;
98
+ }
99
+
100
+ /**
101
+ * Build a Better Auth instance parameterised by deps.
102
+ *
103
+ * Convention mirrors `apps-paid/marketing-api/src/routes/v1/account/license.ts`:
104
+ * the factory accepts optional deps with defaults to real implementations so
105
+ * existing callers (`import { auth }`) are unchanged.
106
+ */
107
+ export function createAuth(deps: CreateAuthDeps = {}) {
108
+ const prisma = deps.prisma ?? getDefaultPrisma();
109
+ return betterAuth({
110
+ database: prismaAdapter(prisma, {
111
+ provider: "postgresql",
112
+ }),
113
+ secret: resolveSecret(),
114
+ baseURL: process.env.BETTER_AUTH_URL,
115
+ socialProviders: buildSocialProviders(),
116
+ // Persist sessions in the database so admin revocation by row delete remains
117
+ // the model the dual-mode preHandler relies on.
118
+ session: {
119
+ expiresIn: 60 * 60 * 24 * 7, // 7 days
120
+ updateAge: 60 * 60 * 24, // refresh in DB once per day
121
+ },
122
+ plugins: [nextCookies()],
123
+ });
124
+ }
125
+
126
+ // Lazy singleton: resolved on first access, not at module-load time.
127
+ // Defers the `require("./lib/prisma-for-auth")` call until auth is actually
128
+ // used, so tests can `import { createAuth }` without triggering a DB connection.
129
+ let _auth: ReturnType<typeof createAuth> | undefined;
130
+
131
+ export const auth: ReturnType<typeof createAuth> = new Proxy(
132
+ {} as ReturnType<typeof createAuth>,
133
+ {
134
+ get(_, prop: string | symbol) {
135
+ if (!_auth) _auth = createAuth();
136
+ return _auth[prop as keyof ReturnType<typeof createAuth>];
137
+ },
138
+ },
139
+ );
140
+
141
+ export type Auth = typeof auth;
@@ -0,0 +1,60 @@
1
+ "use client";
2
+
3
+ // ThemeToggle — narrow client component for theme switching (#1322)
4
+ //
5
+ // OSS template mirror of apps-paid/marketing-web/components/ThemeToggle.tsx.
6
+ // Reads current theme from document.documentElement.dataset.theme, toggles
7
+ // between "light" and "dark", persists to the ailk-theme cookie, and updates
8
+ // the DOM attribute synchronously.
9
+ //
10
+ // This is the ONLY "use client" component added to the layout tree.
11
+
12
+ import type { ReactElement } from "react";
13
+ import { useCallback, useEffect, useState } from "react";
14
+
15
+ const COOKIE_NAME = process.env.NEXT_PUBLIC_THEME_COOKIE_NAME ?? "ailk-theme";
16
+
17
+ function readCurrentTheme(): "light" | "dark" {
18
+ const attr = document.documentElement.getAttribute("data-theme");
19
+ return attr === "dark" ? "dark" : "light";
20
+ }
21
+
22
+ function setCookieTheme(theme: "light" | "dark"): void {
23
+ document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax`;
24
+ }
25
+
26
+ interface Props {
27
+ "data-testid"?: string;
28
+ }
29
+
30
+ export default function ThemeToggle({
31
+ "data-testid": testId,
32
+ }: Props): ReactElement {
33
+ const [theme, setTheme] = useState<"light" | "dark">("light");
34
+
35
+ useEffect(() => {
36
+ const current = readCurrentTheme();
37
+ setTheme(current);
38
+ }, []);
39
+
40
+ const toggle = useCallback(() => {
41
+ const next = theme === "light" ? "dark" : "light";
42
+ document.documentElement.setAttribute("data-theme", next);
43
+ setCookieTheme(next);
44
+ setTheme(next);
45
+ }, [theme]);
46
+
47
+ const isDark = theme === "dark";
48
+
49
+ return (
50
+ <button
51
+ type="button"
52
+ aria-pressed={isDark}
53
+ aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
54
+ data-testid={testId}
55
+ onClick={toggle}
56
+ >
57
+ {isDark ? "Light" : "Dark"}
58
+ </button>
59
+ );
60
+ }
@@ -0,0 +1,34 @@
1
+ // Google Analytics gtag wiring (#886). When NEXT_PUBLIC_GOOGLE_TAG_ID is set,
2
+ // every page emits the canonical gtag.js loader script + inline init script in
3
+ // <head>. When unset, renders null — no GA traffic at all.
4
+ //
5
+ // Sam's call (#886): fires whenever NEXT_PUBLIC_GOOGLE_TAG_ID is set, regardless
6
+ // of STEALTH. Pre-launch internal traffic will hit GA; filter in GA if needed.
7
+ //
8
+ // Mirrored by sibling components in apps-paid/marketing-web/ and
9
+ // apps-paid/dashboard-web/. Three-way duplication is deliberate — keeps each
10
+ // app self-contained for the OSS fork model (apps/web ships in the public
11
+ // template).
12
+ import Script from "next/script";
13
+ import type { ReactElement } from "react";
14
+
15
+ export function GoogleAnalytics(): ReactElement | null {
16
+ const tagId = process.env.NEXT_PUBLIC_GOOGLE_TAG_ID;
17
+ if (!tagId) {
18
+ return null;
19
+ }
20
+ return (
21
+ <>
22
+ <Script
23
+ src={`https://www.googletagmanager.com/gtag/js?id=${tagId}`}
24
+ strategy="afterInteractive"
25
+ />
26
+ <Script id="ga-init" strategy="afterInteractive">
27
+ {`window.dataLayer = window.dataLayer || [];
28
+ function gtag(){dataLayer.push(arguments);}
29
+ gtag('js', new Date());
30
+ gtag('config', '${tagId}');`}
31
+ </Script>
32
+ </>
33
+ );
34
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Blog
3
+ description: Writing on AEO, answer-engine citation readiness, and what it means to ship software with three peers — UX, API, and MCP.
4
+ author:
5
+ name: The AI Launch Kit Team
6
+ tags:
7
+ - AEO
8
+ - AX
9
+ ---
10
+
11
+ # Blog
12
+
13
+ This is where we think out loud. The topics stay close to the work: schema.org implementation details, how LLMs decide what to cite, and what AX-first looks like in practice.
14
+
15
+ The posts are opinionated because vague advice isn't useful. If something here turns out to be wrong, we'll update it and say so.
@@ -0,0 +1,20 @@
1
+ ---
2
+ title: Launching AI Launch Kit
3
+ description: A short note on why we built an AX-first OSS template and what ships in v1.
4
+ date: "2026-05-06"
5
+ author:
6
+ name: The AI Launch Kit Team
7
+ tags:
8
+ - Announcements
9
+ - AX
10
+ ---
11
+
12
+ # Launching AI Launch Kit
13
+
14
+ We're shipping the first public version today. The template gives you three
15
+ peer apps (web, api, mcp), a content-adapter contract with MDX as the default
16
+ backend, and a JSON-LD pipeline for SEO that doesn't require manual schema.org
17
+ authoring.
18
+
19
+ The goal is clone-to-shipping in under an hour. If you hit friction, the
20
+ README's standalone test should catch it before you do.
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Getting Started
3
+ description: Everything you need to know to get your AI Launch Kit project running.
4
+ ---
5
+
6
+ # Getting Started
7
+
8
+ Welcome to AI Launch Kit. This guide walks you through the first steps after cloning the template.
9
+
10
+ ## Prerequisites
11
+
12
+ - Node.js 20+
13
+ - pnpm 9+
14
+
15
+ ## Quick start
16
+
17
+ ```bash
18
+ pnpm install
19
+ pnpm dev
20
+ ```
21
+
22
+ The dev server starts three apps in parallel:
23
+
24
+ - **web** (`localhost:3000`) — the Next.js UX surface
25
+ - **api** (`localhost:3001`) — the Fastify REST API
26
+ - **mcp** — the MCP server (stdio transport)
27
+
28
+ ## Next steps
29
+
30
+ Replace the seed content in `apps/web/content/en/` with your own pages, blog posts, and docs. Each MDX file maps to a route automatically via Fumadocs.
@@ -0,0 +1,36 @@
1
+ // i18n S2 — next-intl getRequestConfig entry point (#949)
2
+ //
3
+ // Reads the message catalog for the requested locale and falls back to
4
+ // English when no catalog exists (e.g. a locale is enabled in project.yaml
5
+ // but its messages/<locale>.json has not been translated yet).
6
+ //
7
+ // Spec: docs/internal/specs/i18n-phase1-s2-next-intl-foundation-spec.md
8
+
9
+ import type { AbstractIntlMessages } from "next-intl";
10
+ import { getRequestConfig } from "next-intl/server";
11
+
12
+ const DEFAULT_LOCALE = "en";
13
+
14
+ export default getRequestConfig(async ({ requestLocale }) => {
15
+ // requestLocale is the modern API (next-intl >= 3.22). It resolves the
16
+ // active locale from the request, validated by middleware.
17
+ const locale = (await requestLocale) ?? DEFAULT_LOCALE;
18
+
19
+ // Attempt to load the locale catalog; fall back to English on any error.
20
+ let messages: AbstractIntlMessages;
21
+ try {
22
+ // Dynamic import — bundler can tree-shake per-locale at build time.
23
+ const mod = await import(`../messages/${locale}.json`).catch(() => null);
24
+ if (mod) {
25
+ messages = (mod.default ?? mod) as AbstractIntlMessages;
26
+ } else {
27
+ const fallback = await import(`../messages/${DEFAULT_LOCALE}.json`);
28
+ messages = (fallback.default ?? fallback) as AbstractIntlMessages;
29
+ }
30
+ } catch {
31
+ const fallback = await import(`../messages/${DEFAULT_LOCALE}.json`);
32
+ messages = (fallback.default ?? fallback) as AbstractIntlMessages;
33
+ }
34
+
35
+ return { locale, messages };
36
+ });