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,81 @@
1
+ /**
2
+ * GET /.well-known/ai-plugin.json
3
+ *
4
+ * Returns the agent plugin manifest for AILK. The shape conforms to the
5
+ * AgentPluginManifestSchema defined in @working-theory/agent-contract.
6
+ *
7
+ * Fields with no source today default to 'none' / placeholder values and
8
+ * are documented here for Phase 2 enrichment:
9
+ * - logo_url: no asset yet; omitted
10
+ * - contact_email: no org email configured; omitted
11
+ * - legal_info_url: no legal page yet; omitted
12
+ * - api.url: no deployed OpenAPI spec yet; defaults to /openapi.json on same origin
13
+ *
14
+ * ─── Consumer re-validation expectation ─────────────────────────────────────
15
+ * This manifest is published over a public, cacheable HTTP route. Consumers
16
+ * (LLM agents, gateways, validators) MUST re-verify the manifest contents at
17
+ * fetch time — DO NOT trust a cached or out-of-band copy. In particular:
18
+ *
19
+ * - `description_for_model`: an agent host should bound its length and
20
+ * scan for prompt-injection patterns before passing it into a model
21
+ * context window.
22
+ * - `auth`: confirm the declared scheme matches the API's actual auth
23
+ * enforcement at the OpenAPI endpoint.
24
+ * - `api.url`: re-fetch the OpenAPI spec and validate the schema before
25
+ * synthesizing tool definitions.
26
+ *
27
+ * See `docs/security/agent-manifest.md` for the full consumer guidance.
28
+ */
29
+
30
+ import { resolveBaseUrl } from "../../../src/lib/routes";
31
+
32
+ /** Soft cap to detect runaway `description_for_model` growth. */
33
+ export const DESCRIPTION_FOR_MODEL_SOFT_CAP_CHARS = 8_000;
34
+
35
+ const MANIFEST_DESCRIPTION_FOR_MODEL =
36
+ "AI Launch Kit (AILK) is an open-source template for shipping AI-native applications. Use it to explore the product catalog, purchase a license, or query documentation.";
37
+
38
+ /**
39
+ * Validates that a manifest's `description_for_model` is within reasonable
40
+ * bounds. Logs a warning when oversized; returns a boolean for callers that
41
+ * want to short-circuit (we choose to still serve the manifest, since
42
+ * truncating silently would be worse than warning).
43
+ */
44
+ export function validateDescriptionForModel(description: string): boolean {
45
+ if (description.length > DESCRIPTION_FOR_MODEL_SOFT_CAP_CHARS) {
46
+ console.warn(
47
+ `[ai-plugin.json] description_for_model exceeds soft cap (${description.length} > ${DESCRIPTION_FOR_MODEL_SOFT_CAP_CHARS} chars). Consumers may rate-limit or reject oversized manifests.`,
48
+ );
49
+ return false;
50
+ }
51
+ return true;
52
+ }
53
+
54
+ // Startup-time validation: fire once on module load so over-sized manifests
55
+ // are flagged before the first request lands.
56
+ validateDescriptionForModel(MANIFEST_DESCRIPTION_FOR_MODEL);
57
+
58
+ export function GET(): Response {
59
+ const baseUrl = resolveBaseUrl();
60
+
61
+ const manifest = {
62
+ schema_version: "v1",
63
+ name_for_human: "AI Launch Kit",
64
+ name_for_model: "ai_launch_kit",
65
+ description_for_human:
66
+ "AX-first OSS template — ship software with API + MCP as canonical agent surfaces.",
67
+ description_for_model: MANIFEST_DESCRIPTION_FOR_MODEL,
68
+ auth: { type: "none" },
69
+ api: {
70
+ type: "openapi",
71
+ url: `${baseUrl}/openapi.json`,
72
+ },
73
+ };
74
+
75
+ return Response.json(manifest, {
76
+ status: 200,
77
+ headers: {
78
+ "cache-control": "public, max-age=86400",
79
+ },
80
+ });
81
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Pure builder for the /.well-known/mcp.json manifest.
3
+ *
4
+ * Extracted from the route handler so the manifest shape can be unit-tested
5
+ * without booting Next.js. Reads the canonical tool catalog from
6
+ * @working-theory/metadata; does no I/O of its own.
7
+ *
8
+ * Issue #209 (spec: docs/internal/specs/draft/ailk-mcp-mvp-pa-i6-discovery-route-spec.md).
9
+ * Issue #300 — moved to /.well-known/mcp.json; added name, description, transport,
10
+ * and outputSchema per MCP discovery spec.
11
+ * Issue #918 — added optional transports[] array to advertise HTTP transport
12
+ * endpoint when MCP_PUBLIC_HTTP_URL is configured.
13
+ */
14
+
15
+ import { getMcpToolCatalog, type McpToolCatalogEntry } from "@working-theory/metadata";
16
+
17
+ /**
18
+ * Inline transport descriptor shape — mirrors McpTransportDescriptor from
19
+ * @working-theory/agent-contract without creating a cross-boundary import from apps/web
20
+ * into packages/agent-contract (which is not on the architecture.yaml allowlist
21
+ * for apps/web). The canonical schema lives in agent-contract; this is a
22
+ * structural copy for the build-manifest module.
23
+ */
24
+ type TransportDescriptor =
25
+ | { type: "stdio"; label?: string }
26
+ | { type: "http"; url: string; version?: string; label?: string };
27
+
28
+ export interface McpManifest {
29
+ /** Manifest schema version. Bump on breaking shape changes only. */
30
+ schemaVersion: "1";
31
+ /** Human-readable server name. */
32
+ name: string;
33
+ /** Human-readable server description. */
34
+ description: string;
35
+ /** Absolute URL the agent connects to for the MCP protocol session. */
36
+ mcpEndpoint: string;
37
+ /** Tool catalog — names, descriptions, input schemas, and output schemas. */
38
+ tools: McpToolCatalogEntry[];
39
+ /**
40
+ * Primary transport for legacy MCP discovery clients (schemaVersion "1").
41
+ * New clients should use `transports[]`.
42
+ */
43
+ transport: {
44
+ type: "http";
45
+ version: string;
46
+ };
47
+ /**
48
+ * Available transport descriptors for this MCP server.
49
+ * Populated when the operator configures standalone transport endpoints.
50
+ * Agents SHOULD prefer this array over the singular `transport` field.
51
+ */
52
+ transports?: TransportDescriptor[];
53
+ /** AILK installation metadata (version etc.). */
54
+ ailk: {
55
+ version: string;
56
+ };
57
+ }
58
+
59
+ export interface BuildMcpManifestOptions {
60
+ /** Site base URL — used to derive the MCP endpoint. */
61
+ baseUrl: string;
62
+ /** AILK package version surfaced to discovering agents. */
63
+ ailkVersion: string;
64
+ /**
65
+ * Optional: absolute URL of the standalone MCP HTTP transport
66
+ * (i.e. the URL of apps/mcp when running in HTTP mode).
67
+ * When set, the manifest advertises this as an additional transport endpoint.
68
+ * Typically sourced from the MCP_PUBLIC_HTTP_URL env var in the web app.
69
+ */
70
+ mcpHttpUrl?: string;
71
+ }
72
+
73
+ /** Path the MCP server is mounted at, relative to baseUrl. */
74
+ const MCP_ENDPOINT_PATH = "/api/mcp";
75
+
76
+ /** MCP HTTP transport version this server implements. */
77
+ const MCP_HTTP_VERSION = "2025-03-26";
78
+
79
+ /**
80
+ * Builds the /.well-known/mcp.json manifest. Pure: same input → same output.
81
+ */
82
+ export function buildMcpManifest(opts: BuildMcpManifestOptions): McpManifest {
83
+ const transports: TransportDescriptor[] = [
84
+ { type: "stdio", label: "Local stdio (subprocess)" },
85
+ ];
86
+
87
+ if (opts.mcpHttpUrl) {
88
+ transports.push({
89
+ type: "http",
90
+ url: opts.mcpHttpUrl,
91
+ version: MCP_HTTP_VERSION,
92
+ label: "HTTP (Streamable HTTP transport)",
93
+ });
94
+ }
95
+
96
+ return {
97
+ schemaVersion: "1",
98
+ name: "AI Launch Kit",
99
+ description:
100
+ "AX-first OSS template — ship software with API + MCP as canonical agent surfaces.",
101
+ mcpEndpoint: `${opts.baseUrl}${MCP_ENDPOINT_PATH}`,
102
+ tools: getMcpToolCatalog(),
103
+ transport: {
104
+ type: "http",
105
+ version: MCP_HTTP_VERSION,
106
+ },
107
+ transports,
108
+ ailk: { version: opts.ailkVersion },
109
+ };
110
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * GET /.well-known/mcp.json
3
+ *
4
+ * Per-site MCP discovery manifest. Agents land on a customer site, hit this
5
+ * URL, and discover the MCP endpoint + tool catalog without out-of-band
6
+ * coordination. Issue #209 (initial), #300 (moved to correct URL + CORS).
7
+ *
8
+ * Pure assembly — no fetch(). The catalog is read in-process from
9
+ * @working-theory/metadata; the AILK version is read from this app's package.json.
10
+ *
11
+ * Mirrors /.well-known/ai-plugin.json conventions: Response.json with a
12
+ * conservative cache-control header.
13
+ *
14
+ * CORS: MCP discovery clients are cross-origin by design. The open GET is
15
+ * public — no credentials, no session-scoped data — so Access-Control-Allow-Origin: *
16
+ * is appropriate per the MCP discovery spec.
17
+ */
18
+
19
+ import packageJson from "../../../package.json";
20
+ import { resolveBaseUrl } from "../../../src/lib/routes";
21
+
22
+ import { buildMcpManifest } from "./build-manifest";
23
+
24
+ const CORS_HEADERS = {
25
+ "Access-Control-Allow-Origin": "*",
26
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
27
+ "Access-Control-Allow-Headers": "Content-Type",
28
+ } as const;
29
+
30
+ export function OPTIONS(): Response {
31
+ return new Response(null, {
32
+ status: 204,
33
+ headers: CORS_HEADERS,
34
+ });
35
+ }
36
+
37
+ export function GET(): Response {
38
+ // MCP_PUBLIC_HTTP_URL advertises the standalone HTTP transport endpoint
39
+ // (apps/mcp running in HTTP mode). When set, the manifest includes it
40
+ // as an additional transport descriptor for discovering agents.
41
+ const mcpHttpUrlEnv = process.env["MCP_PUBLIC_HTTP_URL"];
42
+
43
+ const manifest = buildMcpManifest({
44
+ baseUrl: resolveBaseUrl(),
45
+ ailkVersion: packageJson.version,
46
+ // Spread conditional to satisfy exactOptionalPropertyTypes: omit the key
47
+ // entirely when the env var is not set rather than passing undefined.
48
+ ...(mcpHttpUrlEnv ? { mcpHttpUrl: mcpHttpUrlEnv } : {}),
49
+ });
50
+
51
+ return Response.json(manifest, {
52
+ status: 200,
53
+ headers: {
54
+ "cache-control": "public, max-age=86400",
55
+ ...CORS_HEADERS,
56
+ },
57
+ });
58
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Tests for LocaleLayout — verifies the <html dir> attribute is set
3
+ * correctly based on the active locale.
4
+ *
5
+ * The layout is an async server component; jest renders it by awaiting
6
+ * the returned ReactElement promise. Heavy Next.js/next-intl deps are
7
+ * mocked so the test focuses on the dir attribute wiring only.
8
+ */
9
+ import React from "react";
10
+ import { render } from "@testing-library/react";
11
+
12
+ // ── Mock heavy/Next.js-specific dependencies ──────────────────────────
13
+ jest.mock("next/navigation", () => ({
14
+ usePathname: jest.fn().mockReturnValue("/en/"),
15
+ }));
16
+
17
+ jest.mock("next/headers", () => ({
18
+ headers: jest.fn().mockResolvedValue({
19
+ get: jest.fn().mockReturnValue("/"),
20
+ }),
21
+ }));
22
+
23
+ jest.mock("next-intl/server", () => ({
24
+ getMessages: jest.fn().mockResolvedValue({}),
25
+ getTranslations: jest.fn().mockImplementation(({ namespace }: { namespace: string }) => {
26
+ if (namespace === "nav") {
27
+ const t = (key: string) => key;
28
+ return Promise.resolve(t);
29
+ }
30
+ const t = (key: string) => key;
31
+ return Promise.resolve(t);
32
+ }),
33
+ }));
34
+
35
+ jest.mock("next-intl", () => ({
36
+ NextIntlClientProvider: ({ children }: { children: React.ReactNode }) => (
37
+ <>{children}</>
38
+ ),
39
+ }));
40
+
41
+ jest.mock("@working-theory/web-i18n", () => ({
42
+ readLocales: jest.fn().mockReturnValue(["en"]),
43
+ buildHreflangAlternates: jest.fn().mockReturnValue({}),
44
+ getStealthRobots: jest.fn().mockReturnValue({}),
45
+ }));
46
+
47
+ jest.mock("@working-theory/analytics", () => ({
48
+ Telemetry: () => null,
49
+ VerificationMeta: () => null,
50
+ }));
51
+
52
+ // ── Import subject under test ──────────────────────────────────────────
53
+ import LocaleLayout, { generateMetadata } from "../layout";
54
+
55
+ async function renderLayout(locale: string): Promise<void> {
56
+ const element = LocaleLayout({
57
+ children: <span data-testid="child">content</span>,
58
+ params: Promise.resolve({ locale }),
59
+ });
60
+ const resolved = await element;
61
+ render(resolved);
62
+ }
63
+
64
+ describe("generateMetadata()", () => {
65
+ it("returns a title and description", async () => {
66
+ const metadata = await generateMetadata();
67
+ expect(metadata.title).toBe("AI Launch Kit");
68
+ expect(metadata.description).toMatch(/OSS/);
69
+ });
70
+
71
+ it("includes alternates.languages from buildHreflangAlternates", async () => {
72
+ const metadata = await generateMetadata();
73
+ expect(metadata.alternates).toBeDefined();
74
+ expect(metadata.alternates?.languages).toBeDefined();
75
+ });
76
+ });
77
+
78
+ describe("LocaleLayout — chrome smoke (#1451)", () => {
79
+ it("renders Nav with aria-label=Primary", async () => {
80
+ await renderLayout("en");
81
+ expect(document.querySelector("nav[aria-label='Primary']")).toBeInTheDocument();
82
+ });
83
+
84
+ it("renders Footer element", async () => {
85
+ await renderLayout("en");
86
+ expect(document.querySelector("footer")).toBeInTheDocument();
87
+ });
88
+
89
+ it("renders nav logo link pointing to locale home", async () => {
90
+ await renderLayout("en");
91
+ const logoLink = document.querySelector("[data-testid='brand-logo-link']");
92
+ expect(logoLink).toBeInTheDocument();
93
+ // as-needed dialect (#3450): bare for the default locale.
94
+ expect(logoLink?.getAttribute("href")).toBe("/");
95
+ });
96
+ });
97
+
98
+ describe("LocaleLayout — <html dir> attribute", () => {
99
+ // React 19 + jsdom: <html> is a singleton (document.documentElement);
100
+ // RTL merges the rendered <html> attributes onto the live document root.
101
+ it('renders dir="ltr" for locale "en"', async () => {
102
+ await renderLayout("en");
103
+ expect(document.documentElement.getAttribute("dir")).toBe("ltr");
104
+ });
105
+
106
+ it('renders dir="rtl" for locale "ar"', async () => {
107
+ await renderLayout("ar");
108
+ expect(document.documentElement.getAttribute("dir")).toBe("rtl");
109
+ });
110
+
111
+ it('preserves lang attribute for locale "en"', async () => {
112
+ await renderLayout("en");
113
+ expect(document.documentElement.getAttribute("lang")).toBe("en");
114
+ });
115
+
116
+ it('preserves lang attribute for locale "ar"', async () => {
117
+ await renderLayout("ar");
118
+ expect(document.documentElement.getAttribute("lang")).toBe("ar");
119
+ });
120
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * HTML snapshot regression tests for the apps/web → @working-theory/page-renderer migration.
3
+ *
4
+ * Node version: captured against Node 22.x (same as CI).
5
+ * MDX seed content: DI-injected stub — no fs reads, deterministic across environments.
6
+ *
7
+ * Two-commit strategy (per spec §8 Open Questions):
8
+ * Commit 1: this file added; snapshots captured against pre-migration routes (RED-passing).
9
+ * Commit 2: routes migrated; same snapshots verify diff=0 (GREEN).
10
+ *
11
+ * Each of the four routes is tested in two states:
12
+ * - empty: getMdxSource returns null → empty-state branch
13
+ * - populated: getMdxSource returns a minimal MdxPageSource → full render
14
+ *
15
+ * Failure = snapshot diff > 0 bytes. DO NOT regenerate snapshots to pass.
16
+ * A non-zero diff indicates a behavior regression in the migration. Escalate.
17
+ */
18
+ import React from "react";
19
+ import { render } from "@testing-library/react";
20
+ import { stripJsonLd } from "@working-theory/page-renderer/testing";
21
+
22
+ // ── @working-theory/ui mock ─────────────────────────────────────────────────────────────
23
+ // SectionRenderer renders a predictable deterministic div so the snapshot is
24
+ // stable across section-component implementations.
25
+ jest.mock("@working-theory/ui", () => ({
26
+ SectionRenderer: ({ sections }: { sections: unknown[] }) =>
27
+ React.createElement("div", {
28
+ "data-testid": "section-renderer",
29
+ "data-section-count": String(sections.length),
30
+ }),
31
+ }));
32
+
33
+ // ── Import page components ─────────────────────────────────────────────────────
34
+ import HomePage from "../page";
35
+ import AboutPage from "../about/page";
36
+ import FAQPage from "../faq/page";
37
+ import LaunchServicePage from "../launch-service/page";
38
+
39
+ // ── Shared stubs ───────────────────────────────────────────────────────────────
40
+
41
+ function makeSource(
42
+ type: string,
43
+ slug: string,
44
+ title: string,
45
+ description: string,
46
+ ) {
47
+ return {
48
+ frontmatter: {
49
+ type: type as "HomePage",
50
+ slug,
51
+ title,
52
+ description,
53
+ },
54
+ body: "",
55
+ };
56
+ }
57
+
58
+ const getMdxSourceEmpty = jest.fn().mockResolvedValue(null);
59
+ const getPageSectionsEmpty = jest.fn().mockResolvedValue([]);
60
+
61
+ // ── HomePage ───────────────────────────────────────────────────────────────────
62
+
63
+ describe("HomePage — snapshot regression", () => {
64
+ it("empty state renders deterministic HTML", async () => {
65
+ const element = await HomePage({
66
+ params: Promise.resolve({ locale: "en" }),
67
+ getMdxSource: getMdxSourceEmpty,
68
+ getPageSections: getPageSectionsEmpty,
69
+ });
70
+ const { container } = render(element);
71
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
72
+ `"<main><h1>AI Launch Kit</h1><p data-testid="home-description">AX-first OSS template for shipping software with API + MCP as canonical surfaces.</p></main>"`,
73
+ );
74
+ });
75
+
76
+ it("populated state renders deterministic HTML", async () => {
77
+ const source = makeSource(
78
+ "HomePage",
79
+ "home",
80
+ "AI Launch Kit",
81
+ "AX-first OSS template for shipping software with API + MCP as canonical surfaces.",
82
+ );
83
+ const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
84
+ const getPageSectionsPopulated = jest.fn().mockResolvedValue([]);
85
+
86
+ const element = await HomePage({
87
+ params: Promise.resolve({ locale: "en" }),
88
+ getMdxSource: getMdxSourcePopulated,
89
+ getPageSections: getPageSectionsPopulated,
90
+ });
91
+ const { container } = render(element);
92
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
93
+ `"<main><h1>AI Launch Kit</h1><p data-testid="home-description">AX-first OSS template for shipping software with API + MCP as canonical surfaces.</p><div data-testid="section-renderer" data-section-count="0"></div></main>"`,
94
+ );
95
+ });
96
+ });
97
+
98
+ // ── AboutPage ──────────────────────────────────────────────────────────────────
99
+
100
+ describe("AboutPage — snapshot regression", () => {
101
+ it("empty state renders deterministic HTML", async () => {
102
+ const element = await AboutPage({
103
+ params: Promise.resolve({ locale: "en" }),
104
+ getMdxSource: getMdxSourceEmpty,
105
+ getPageSections: getPageSectionsEmpty,
106
+ });
107
+ const { container } = render(element);
108
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
109
+ `"<main><h1>About</h1><p data-testid="section-empty-state">About page content coming soon.</p></main>"`,
110
+ );
111
+ });
112
+
113
+ it("populated state renders deterministic HTML", async () => {
114
+ const source = makeSource("AboutPage", "about", "About Us", "Who we are.");
115
+ const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
116
+ const getPageSectionsPopulated = jest.fn().mockResolvedValue([]);
117
+
118
+ const element = await AboutPage({
119
+ params: Promise.resolve({ locale: "en" }),
120
+ getMdxSource: getMdxSourcePopulated,
121
+ getPageSections: getPageSectionsPopulated,
122
+ });
123
+ const { container } = render(element);
124
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
125
+ `"<main><h1>About Us</h1><p data-testid="about-description">Who we are.</p><div data-testid="section-renderer" data-section-count="0"></div></main>"`,
126
+ );
127
+ });
128
+ });
129
+
130
+ // ── FAQPage ────────────────────────────────────────────────────────────────────
131
+
132
+ describe("FAQPage — snapshot regression", () => {
133
+ it("empty state renders deterministic HTML (with scaffold JSON-LD)", async () => {
134
+ const element = await FAQPage({
135
+ params: Promise.resolve({ locale: "en" }),
136
+ getMdxSource: getMdxSourceEmpty,
137
+ getPageSections: getPageSectionsEmpty,
138
+ });
139
+ const { container } = render(element);
140
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
141
+ `"<main><h1>FAQ</h1><p data-testid="section-empty-state">Frequently asked questions content coming soon.</p></main>"`,
142
+ );
143
+ });
144
+
145
+ it("populated state renders deterministic HTML", async () => {
146
+ const source = makeSource(
147
+ "FAQPage",
148
+ "faq",
149
+ "FAQ",
150
+ "Frequently asked questions.",
151
+ );
152
+ const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
153
+ const getPageSectionsPopulated = jest.fn().mockResolvedValue([]);
154
+
155
+ const element = await FAQPage({
156
+ params: Promise.resolve({ locale: "en" }),
157
+ getMdxSource: getMdxSourcePopulated,
158
+ getPageSections: getPageSectionsPopulated,
159
+ });
160
+ const { container } = render(element);
161
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
162
+ `"<main><h1>FAQ</h1><p data-testid="faq-description">Frequently asked questions.</p><div data-testid="section-renderer" data-section-count="0"></div></main>"`,
163
+ );
164
+ });
165
+ });
166
+
167
+ // ── LaunchServicePage ──────────────────────────────────────────────────────────
168
+
169
+ describe("LaunchServicePage — snapshot regression", () => {
170
+ it("empty state renders deterministic HTML", async () => {
171
+ const element = await LaunchServicePage({
172
+ params: Promise.resolve({ locale: "en" }),
173
+ getMdxSource: getMdxSourceEmpty,
174
+ getPageSections: getPageSectionsEmpty,
175
+ });
176
+ const { container } = render(element);
177
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
178
+ `"<main><h1>Launch Service</h1><p data-testid="section-empty-state">Launch Service page content coming soon.</p></main>"`,
179
+ );
180
+ });
181
+
182
+ it("populated state renders deterministic HTML", async () => {
183
+ const source = makeSource(
184
+ "Service",
185
+ "launch-service",
186
+ "Launch Service",
187
+ "Our launch service offering.",
188
+ );
189
+ const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
190
+ const getPageSectionsPopulated = jest.fn().mockResolvedValue([]);
191
+
192
+ const element = await LaunchServicePage({
193
+ params: Promise.resolve({ locale: "en" }),
194
+ getMdxSource: getMdxSourcePopulated,
195
+ getPageSections: getPageSectionsPopulated,
196
+ });
197
+ const { container } = render(element);
198
+ expect(stripJsonLd(container.innerHTML)).toMatchInlineSnapshot(
199
+ `"<main><h1>Launch Service</h1><p data-testid="launch-service-description">Our launch service offering.</p><div data-testid="section-renderer" data-section-count="0"></div></main>"`,
200
+ );
201
+ });
202
+ });
@@ -0,0 +1,31 @@
1
+ // About page. Reads the AboutPage MDX frontmatter, renders sections via
2
+ // SectionRenderer, and emits a JSON-LD <script>.
3
+ import type {
4
+ getMdxSource as defaultGetMdxSource,
5
+ getPageSections as defaultGetPageSections} from "@working-theory/page-renderer";
6
+ import {
7
+ renderPage
8
+ } from "@working-theory/page-renderer";
9
+ import type { ReactElement } from "react";
10
+
11
+ interface Props {
12
+ params: Promise<{ locale: string }>;
13
+ getMdxSource?: typeof defaultGetMdxSource;
14
+ getPageSections?: typeof defaultGetPageSections;
15
+ }
16
+
17
+ export default async function Page({
18
+ params,
19
+ getMdxSource,
20
+ getPageSections,
21
+ }: Props): Promise<ReactElement> {
22
+ const { locale } = await params;
23
+ return renderPage({
24
+ type: "AboutPage",
25
+ slug: "about",
26
+ locale,
27
+ translationsNamespace: "about",
28
+ ...(getMdxSource !== undefined && { getMdxSource }),
29
+ ...(getPageSections !== undefined && { getPageSections }),
30
+ });
31
+ }