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,171 @@
1
+ /**
2
+ * readLocales — read and validate the `product.locales` field from project.yaml.
3
+ *
4
+ * Mirrors `apps/web/lib/locales.ts` and `apps/api/src/lib/locales.ts`.
5
+ * Duplicated because architecture.yaml does not permit cross-app imports
6
+ * (apps/mcp may not import apps/web or apps/api), and a shared package for
7
+ * filesystem helpers would extend the architectural surface beyond the scope
8
+ * of this spec.
9
+ *
10
+ * Validation rules:
11
+ * - `locales` field absent → defaults to `["en"]` (backward-compat with OSS
12
+ * forks that pre-date the field)
13
+ * - `locales` present → must be a non-empty array of BCP-47 locale tags
14
+ * matching `/^[a-z]{2}(-[A-Z]{2})?$/` (MVP subset)
15
+ * - `product.default_locale` must be included in the `locales` list; parse
16
+ * error if not
17
+ * - `locales: []` → parse error (empty array is invalid)
18
+ *
19
+ * Used by content read tools to validate the `locale` parameter against the
20
+ * configured allowlist before passing to the adapter (defense-in-depth per
21
+ * AC-i18n-07).
22
+ */
23
+
24
+ import { readFileSync } from "node:fs";
25
+ import { join } from "node:path";
26
+
27
+ import { parse } from "yaml";
28
+
29
+ /** BCP-47 MVP subset: `xx` or `xx-XX` (language + optional region). */
30
+ const BCP47_RE = /^[a-z]{2}(-[A-Z]{2})?$/;
31
+
32
+ /**
33
+ * Reads, validates, and returns the sorted locale list from project.yaml.
34
+ *
35
+ * @param cwd - Directory to search from. Defaults to `process.cwd()`.
36
+ * @returns Sorted `string[]` of enabled locale tags.
37
+ * @throws Error with a descriptive message (file + cause) when validation fails.
38
+ */
39
+ export function readLocales(cwd = process.cwd()): string[] {
40
+ const filePath = resolveProjectYamlPath(cwd);
41
+
42
+ let raw: string;
43
+ try {
44
+ raw = readFileSync(filePath, "utf8");
45
+ } catch (err) {
46
+ const e = new Error(
47
+ `[locales] Cannot read ${filePath}: ${err instanceof Error ? err.message : String(err)}`,
48
+ );
49
+ (e as { cause?: unknown }).cause = err;
50
+ throw e;
51
+ }
52
+
53
+ let doc: unknown;
54
+ try {
55
+ doc = parse(raw);
56
+ } catch (err) {
57
+ const e = new Error(
58
+ `[locales] YAML parse error in ${filePath}: ${err instanceof Error ? err.message : String(err)}`,
59
+ );
60
+ (e as { cause?: unknown }).cause = err;
61
+ throw e;
62
+ }
63
+
64
+ if (!doc || typeof doc !== "object") {
65
+ throw new Error(
66
+ `[locales] ${filePath}: expected a YAML mapping at the top level`,
67
+ );
68
+ }
69
+
70
+ const top = doc as Record<string, unknown>;
71
+ const product = top["product"];
72
+
73
+ const p =
74
+ product && typeof product === "object"
75
+ ? (product as Record<string, unknown>)
76
+ : {};
77
+
78
+ const defaultLocale =
79
+ typeof p["default_locale"] === "string" ? p["default_locale"] : "en";
80
+
81
+ const rawLocales = p["locales"];
82
+
83
+ if (rawLocales === undefined || rawLocales === null) {
84
+ return ["en"];
85
+ }
86
+
87
+ if (!Array.isArray(rawLocales)) {
88
+ throw new Error(
89
+ `[locales] ${filePath}: product.locales must be an array, got ${typeof rawLocales}`,
90
+ );
91
+ }
92
+
93
+ if (rawLocales.length === 0) {
94
+ throw new Error(
95
+ `[locales] ${filePath}: product.locales must be a non-empty array (got empty array)`,
96
+ );
97
+ }
98
+
99
+ for (const entry of rawLocales) {
100
+ if (typeof entry !== "string") {
101
+ throw new Error(
102
+ `[locales] ${filePath}: product.locales entries must be strings, got ${typeof entry}`,
103
+ );
104
+ }
105
+ if (!BCP47_RE.test(entry)) {
106
+ throw new Error(
107
+ `[locales] ${filePath}: product.locales entry "${entry}" is not a valid BCP-47 locale tag (expected format: xx or xx-XX)`,
108
+ );
109
+ }
110
+ }
111
+
112
+ const locales = rawLocales as string[];
113
+
114
+ if (!locales.includes(defaultLocale)) {
115
+ throw new Error(
116
+ `[locales] ${filePath}: product.default_locale "${defaultLocale}" is not in product.locales [${locales.join(", ")}]`,
117
+ );
118
+ }
119
+
120
+ return [...locales].sort();
121
+ }
122
+
123
+ /**
124
+ * Read the project's default locale from project.yaml. Returns `"en"` when
125
+ * project.yaml is absent or `product.default_locale` is unset — same default
126
+ * as the adapter.
127
+ */
128
+ export function readDefaultLocale(cwd = process.cwd()): string {
129
+ const filePath = resolveProjectYamlPath(cwd);
130
+
131
+ let raw: string;
132
+ try {
133
+ raw = readFileSync(filePath, "utf8");
134
+ } catch {
135
+ return "en";
136
+ }
137
+
138
+ let doc: unknown;
139
+ try {
140
+ doc = parse(raw);
141
+ } catch {
142
+ return "en";
143
+ }
144
+
145
+ if (!doc || typeof doc !== "object") return "en";
146
+ const product = (doc as Record<string, unknown>)["product"];
147
+ if (!product || typeof product !== "object") return "en";
148
+ const defaultLocale = (product as Record<string, unknown>)["default_locale"];
149
+ return typeof defaultLocale === "string" ? defaultLocale : "en";
150
+ }
151
+
152
+ /**
153
+ * Probe likely locations for project.yaml. Apps run from their own dirs in
154
+ * dev/build (`apps/mcp/`) but from repo root in some test setups.
155
+ */
156
+ function resolveProjectYamlPath(cwd: string): string {
157
+ const candidates = [
158
+ join(cwd, "project.yaml"),
159
+ join(cwd, "..", "project.yaml"),
160
+ join(cwd, "..", "..", "project.yaml"),
161
+ ];
162
+ for (const candidate of candidates) {
163
+ try {
164
+ readFileSync(candidate, "utf8");
165
+ return candidate;
166
+ } catch {
167
+ // continue
168
+ }
169
+ }
170
+ return candidates[0] ?? join(cwd, "project.yaml");
171
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Paid-tier license guard for apps/mcp content-write tools (D14a Stage 0).
3
+ *
4
+ * Calls `checkLicense()` from @working-theory/license and returns a canonical
5
+ * `payment_required` error body for free tier; returns `null` to signal the
6
+ * caller may proceed.
7
+ *
8
+ * Usage in a tool handler:
9
+ *
10
+ * const guard = await requirePaidTier();
11
+ * if (guard) return guard; // free tier — return canonical error body
12
+ * // paid tier — proceed
13
+ *
14
+ * Pattern intentionally returns the error body directly (vs. throwing) — MCP
15
+ * tools surface errors as their result body, not via thrown exceptions.
16
+ */
17
+
18
+ import { checkLicense } from "@working-theory/license";
19
+
20
+ import { paymentRequiredError, type ErrorBody } from "./errors.js";
21
+
22
+ const FREE_TIER_MESSAGE =
23
+ "This tool requires a paid license. Visit https://ailaunchkit.ai to upgrade.";
24
+
25
+ export async function requirePaidTier(): Promise<ErrorBody | null> {
26
+ let result;
27
+ try {
28
+ result = await checkLicense();
29
+ } catch {
30
+ // Per ADR 0014 — local-verify license; an exception is a bug, not a
31
+ // network failure. Treat as free for safe-by-default gating.
32
+ return paymentRequiredError(FREE_TIER_MESSAGE);
33
+ }
34
+
35
+ // Revoked paid licenses keep verifying until JWT exp (up to 24h after the
36
+ // server reports `revoked`). Closing the gate when `revoked === true`
37
+ // shrinks that window to "next request" without waiting for exp. See
38
+ // packages/license/src/types.ts § LicenseResult and ADR 0014.
39
+ if (result.revoked === true || result.tier === "free") {
40
+ return paymentRequiredError(FREE_TIER_MESSAGE);
41
+ }
42
+
43
+ return null;
44
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * resolveLocale — validate a caller-supplied locale against the project's
3
+ * allowlist. Phase 3 S3.
4
+ *
5
+ * Returns the requested locale when it's present in `readLocales()`;
6
+ * otherwise silently falls back to the project's default locale (AC-i18n-07).
7
+ *
8
+ * Failures reading `project.yaml` degrade to a default-locale-only allowlist
9
+ * — mirrors the same defensive behavior in `apps/api/src/routes/content/*`.
10
+ *
11
+ * This boundary check is defense-in-depth on top of the adapter's BCP-47
12
+ * regex; the adapter still applies its own check and fallback chain on
13
+ * every read.
14
+ */
15
+
16
+ import { readDefaultLocale, readLocales } from "./locales.js";
17
+
18
+ export function resolveLocale(requested: string | undefined): string {
19
+ const defaultLocale = readDefaultLocale();
20
+ if (requested === undefined) return defaultLocale;
21
+
22
+ let allow: string[];
23
+ try {
24
+ allow = readLocales();
25
+ } catch {
26
+ allow = [defaultLocale];
27
+ }
28
+
29
+ return allow.includes(requested) ? requested : defaultLocale;
30
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * MCP server factory.
3
+ *
4
+ * Creates and configures the McpServer instance with all registered tools.
5
+ * The server is returned unconnected — callers attach a transport (stdio in
6
+ * production, in-memory in tests).
7
+ *
8
+ * Tool names + descriptions + input shapes come from the canonical catalog
9
+ * exported by `@working-theory/metadata` (issue #209). The catalog is the single source
10
+ * of truth shared with `apps/web /.well-known/mcp` for agent discovery.
11
+ * Handlers continue to live in `apps/mcp/src/tools/*` — this module wires
12
+ * each catalog entry to its handler.
13
+ */
14
+
15
+ import { getMcpToolRegistrations, OSS_DISABLED_TOOLS } from "@working-theory/metadata";
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+
18
+ import { ailkAudit } from "./tools/audit.js";
19
+ import { ailkCaptureLead } from "./tools/capture_lead.js";
20
+ import { ailkCreatePage } from "./tools/create_page.js";
21
+ import { ailkDeletePage } from "./tools/delete_page.js";
22
+ import { ailkGetBusinessHours } from "./tools/get_business_hours.js";
23
+ import { ailkGetFaq } from "./tools/get_faq.js";
24
+ import { ailkGetLocations } from "./tools/get_locations.js";
25
+ import { ailkGetOfferings } from "./tools/get_offerings.js";
26
+ import { ailkGetPricing } from "./tools/get_pricing.js";
27
+ import { ailkGetSiteInfo } from "./tools/get_site_info.js";
28
+ import { ailkListPages } from "./tools/list_pages.js";
29
+ import { ailkPurchase } from "./tools/purchase.js";
30
+ import { ailkReadContent } from "./tools/read_content.js";
31
+ import { ailkRequestCallback } from "./tools/request_callback.js";
32
+ import { ailkScaffold } from "./tools/scaffold.js";
33
+ import { ailkSearchContent } from "./tools/search_content.js";
34
+ import { ailkSubscribeNewsletter } from "./tools/subscribe_newsletter.js";
35
+ import { ailkUpdatePage } from "./tools/update_page.js";
36
+
37
+ /**
38
+ * Tool-name → handler map. Each handler accepts the parsed input the SDK
39
+ * passes through after Zod validation against the catalog input shape.
40
+ *
41
+ * Cast via `never` is safe: each handler validates its input via its own
42
+ * Zod parse before doing real work (see apps/mcp/src/tools/*.ts).
43
+ */
44
+ const HANDLERS: Record<string, (input: unknown) => Promise<unknown>> = {
45
+ start_purchase: (input) => ailkPurchase(input as never),
46
+ list_pages: (input) => ailkListPages(input as never),
47
+ read_content: (input) => ailkReadContent(input as never),
48
+ search_content: (input) => ailkSearchContent(input as never),
49
+ get_site_info: (input) => ailkGetSiteInfo(input as never),
50
+ get_business_hours: (input) => ailkGetBusinessHours(input as never),
51
+ get_locations: (input) => ailkGetLocations(input as never),
52
+ get_offerings: (input) => ailkGetOfferings(input as never),
53
+ get_pricing: (input) => ailkGetPricing(input as never),
54
+ get_faq: (input) => ailkGetFaq(input as never),
55
+ audit: (input) => {
56
+ const { slug, type, url } = input as {
57
+ slug?: string;
58
+ type?: string;
59
+ url?: string;
60
+ };
61
+ const handlerInput = url !== undefined ? { url } : { slug: slug!, type };
62
+ return ailkAudit(handlerInput as never);
63
+ },
64
+ capture_lead: (input) => ailkCaptureLead(input as never),
65
+ subscribe_newsletter: (input) => ailkSubscribeNewsletter(input as never),
66
+ request_callback: (input) => ailkRequestCallback(input as never),
67
+ scaffold: (input) => ailkScaffold(input as never),
68
+ // D14a Stage 1 Unit C (#55): Paid-tier content write surface. Handlers
69
+ // self-gate via requirePaidTier and return canonical error bodies (no throw)
70
+ // for license / validation / conflict / not-found cases — see
71
+ // apps/mcp/src/lib/errors.ts. Catalog entries carry tier: "pro" so agents
72
+ // see the gating before invoking via list_tools.
73
+ create_page: (input) => ailkCreatePage(input as never),
74
+ update_page: (input) => ailkUpdatePage(input as never),
75
+ delete_page: (input) => ailkDeletePage(input as never),
76
+ };
77
+
78
+ /**
79
+ * Tool descriptor returned by {@link listRegisteredTools}. A minimal shape
80
+ * sufficient for callers that need to assert which tools are registered
81
+ * (e.g., the S15a regression test) without depending on the SDK's internal
82
+ * `_registeredTools` field.
83
+ */
84
+ export interface RegisteredToolDescriptor {
85
+ name: string;
86
+ description: string;
87
+ }
88
+
89
+ /**
90
+ * Returns the list of tools that {@link createMcpServer} will register, after
91
+ * the OSS catalog filter ({@link OSS_DISABLED_TOOLS}) is applied. Pure — no
92
+ * server instance is constructed.
93
+ *
94
+ * Consumed by `apps/mcp/__tests__/purchase-disabled.test.ts` to assert that
95
+ * disabled tools are absent from the registered catalog. If a future change
96
+ * removes a name from `OSS_DISABLED_TOOLS` without satisfying the Phase 3
97
+ * re-enable criteria, that test fails RED.
98
+ */
99
+ export function listRegisteredTools(): RegisteredToolDescriptor[] {
100
+ return getMcpToolRegistrations()
101
+ .filter((reg) => !OSS_DISABLED_TOOLS.has(reg.name))
102
+ .map((reg) => ({ name: reg.name, description: reg.description }));
103
+ }
104
+
105
+ /**
106
+ * Creates a configured McpServer with all AILK tools registered, less any
107
+ * tool name in {@link OSS_DISABLED_TOOLS}. The disabled filter is applied
108
+ * defensively here even though the upstream `getMcpToolRegistrations()`
109
+ * already excludes them — a belt-and-braces guard so a future regression in
110
+ * the metadata package can't accidentally re-register a disabled tool.
111
+ *
112
+ * Re-enable criteria for each disabled tool live at
113
+ * `docs/internal/roadmaps/phase-3-readiness.md`. S15a (issue #610) is the
114
+ * source of this filter.
115
+ */
116
+ export function createMcpServer(): McpServer {
117
+ const server = new McpServer({
118
+ name: "ailk",
119
+ version: "0.1.0",
120
+ });
121
+
122
+ for (const reg of getMcpToolRegistrations()) {
123
+ if (OSS_DISABLED_TOOLS.has(reg.name)) {
124
+ continue;
125
+ }
126
+ const handler = HANDLERS[reg.name];
127
+ if (!handler) {
128
+ throw new Error(`MCP catalog drift: no handler for tool '${reg.name}'`);
129
+ }
130
+ server.tool(reg.name, reg.description, reg.inputShape, async (input) => {
131
+ const result = await handler(input);
132
+ return {
133
+ content: [{ type: "text", text: JSON.stringify(result) }],
134
+ };
135
+ });
136
+ }
137
+
138
+ return server;
139
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * audit — runs the AEO Layer 1 scoring engine against a single page.
3
+ *
4
+ * Pure read: imports runAudit from @working-theory/aeo (ADR 0011). No auth required.
5
+ * The adapter (audit-single-page) resolves URL or slug to a filesystem read;
6
+ * no fetch() is called anywhere in this file or the adapter.
7
+ *
8
+ * Input: { slug, type? } | { url }
9
+ * slug branch — lowercase [a-z0-9-]+ slug; type optional (defaults BlogPosting)
10
+ * url branch — absolute URL on the AILK installation's own domain only
11
+ * (same-origin check uses AILK_BASE_URL || NEXT_PUBLIC_BASE_URL)
12
+ *
13
+ * Output: { score, perRule, perPage, meta }
14
+ * score — 0–100 overall
15
+ * perRule — per-rule breakdown (id, name, weight, status, evidence?, advice?)
16
+ * perPage — per-page summary (slug, type, score)
17
+ * meta — { auditedAt, ruleSet, locale }
18
+ *
19
+ * Session/scope: none — read-only public content.
20
+ */
21
+
22
+ import {
23
+ runAudit,
24
+ alwaysFails,
25
+ alwaysPasses,
26
+ entityMarkup,
27
+ faqHowtoIncentive,
28
+ jsonldValidity,
29
+ schemaCoverage,
30
+ type Rule,
31
+ } from "@working-theory/aeo";
32
+ import {
33
+ auditInputSchema,
34
+ auditOutputSchema,
35
+ type AuditInput,
36
+ type AuditOutput,
37
+ } from "@working-theory/validation";
38
+
39
+ import {
40
+ buildSinglePageAuditInput,
41
+ type SinglePageInput,
42
+ } from "../lib/audit-single-page.js";
43
+
44
+ export { auditInputSchema, auditOutputSchema };
45
+ export type { AuditInput, AuditOutput };
46
+
47
+ /**
48
+ * Canonical rule set — mirrors packages/aeo/src/cli/index.ts RULES array.
49
+ * Imported from @working-theory/aeo public API; must not be re-implemented here.
50
+ */
51
+ const RULES: Rule[] = [
52
+ alwaysPasses,
53
+ alwaysFails,
54
+ schemaCoverage,
55
+ jsonldValidity,
56
+ faqHowtoIncentive,
57
+ entityMarkup,
58
+ ];
59
+
60
+ /**
61
+ * audit handler.
62
+ *
63
+ * Accepts an optional `contentRoot` override for testing. When omitted,
64
+ * resolves the content root relative to cwd (supports ESM runtime).
65
+ */
66
+ export async function ailkAudit(
67
+ input: AuditInput,
68
+ contentRoot?: string,
69
+ ): Promise<AuditOutput> {
70
+ auditInputSchema.parse(input);
71
+
72
+ // Cast: AuditInput's Zod-inferred optional fields include `undefined` in
73
+ // their union, but SinglePageInput uses exactOptionalPropertyTypes where `?`
74
+ // means "absent." The auditInputSchema.parse above guarantees validity.
75
+ const auditInput = await buildSinglePageAuditInput(
76
+ input as unknown as SinglePageInput,
77
+ contentRoot,
78
+ );
79
+ const report = runAudit(auditInput, RULES);
80
+
81
+ const result: AuditOutput = {
82
+ score: report.score,
83
+ perRule: report.perRule.map((r) => ({
84
+ ruleId: r.id,
85
+ name: r.id, // rule description not on PerRuleResult; use id as name
86
+ weight: r.weight,
87
+ status: r.pass ? "pass" : "fail",
88
+ ...(r.evidence !== undefined && { evidence: r.evidence }),
89
+ })),
90
+ perPage: report.perPage.map((p) => ({
91
+ slug: p.slug,
92
+ type: p.type,
93
+ score: p.score,
94
+ })),
95
+ meta: {
96
+ auditedAt: report.meta.auditedAt,
97
+ ruleSet: `${report.meta.schemaVersion}/${report.meta.registryVersion}`,
98
+ locale: auditInput.locale,
99
+ },
100
+ };
101
+
102
+ return auditOutputSchema.parse(result);
103
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * capture_lead — capture a contact-form lead.
3
+ *
4
+ * Validates input via Zod, forwards to POST /api/leads/submit via
5
+ * @working-theory/api-client, and shapes the response into the MCP output schema.
6
+ * No business logic — orchestration lives in apps/api B6.
7
+ *
8
+ * Paired with captureLeadAction Server Action per ADR 0013.
9
+ */
10
+
11
+ import { apiClient } from "@working-theory/api-client";
12
+ import {
13
+ submitLeadInputSchema,
14
+ submitLeadOutputSchema,
15
+ type SubmitLeadInput,
16
+ type SubmitLeadOutput,
17
+ } from "@working-theory/validation";
18
+
19
+ export { submitLeadInputSchema, submitLeadOutputSchema };
20
+ export type { SubmitLeadInput, SubmitLeadOutput };
21
+
22
+ export async function ailkCaptureLead(
23
+ input: SubmitLeadInput,
24
+ ): Promise<SubmitLeadOutput> {
25
+ const parsed = submitLeadInputSchema.safeParse(input);
26
+ if (!parsed.success) {
27
+ return {
28
+ ok: false,
29
+ error: { kind: "invalid_input", issues: parsed.error.issues },
30
+ };
31
+ }
32
+
33
+ const { name, email, message, phone } = parsed.data;
34
+ const siteId = process.env.AILK_SITE_ID ?? "default";
35
+ const body = {
36
+ siteId,
37
+ source: "mcp",
38
+ email,
39
+ payload: {
40
+ name,
41
+ ...(message !== undefined && { message }),
42
+ ...(phone !== undefined && { phone }),
43
+ },
44
+ };
45
+
46
+ const res = await apiClient.leads.submit(body);
47
+ if (!res.ok) {
48
+ return { ok: false, error: { kind: "api_error", status: res.status } };
49
+ }
50
+
51
+ return { ok: true, id: res.body.id, persisted: res.body.persisted };
52
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * create_page — Paid-tier MCP tool for authoring a new content page.
3
+ *
4
+ * D14a Stage 1 Unit C — wires the McpServer create_page handler to the
5
+ * ContentAdapter.createPage method (Stage 1 Unit A implements the MDX
6
+ * adapter; tests inject a fake adapter).
7
+ *
8
+ * Surface contract: validates input via FrontmatterSchema.extend({ body }),
9
+ * runs the Paid-tier license guard, calls adapter.createPage, and maps adapter
10
+ * errors to canonical MCP error bodies (see apps/mcp/src/lib/errors.ts).
11
+ *
12
+ * Input shape mirrors the Stage 0 catalog entry in
13
+ * packages/metadata/src/mcp-tool-catalog.ts. Catalog parses a permissive
14
+ * outer shape (`page: record`); this handler parses strictly via
15
+ * FrontmatterSchema so bad frontmatter surfaces as a validation_failed body.
16
+ */
17
+
18
+ import { join } from "node:path";
19
+
20
+ import {
21
+ FrontmatterSchema,
22
+ createMdxAdapter,
23
+ mapAdapterError,
24
+ type ContentAdapter,
25
+ } from "@working-theory/content-adapters";
26
+ import { z } from "zod";
27
+
28
+ import { validationError, type ErrorBody } from "../lib/errors.js";
29
+ import { requirePaidTier } from "../lib/require-paid-tier.js";
30
+
31
+ const CreatePageInputSchema = FrontmatterSchema.extend({
32
+ body: z.string().min(1),
33
+ });
34
+
35
+ interface CreatePageSuccess {
36
+ page: Record<string, unknown>;
37
+ version: string;
38
+ }
39
+
40
+ export type CreatePageResult = CreatePageSuccess | ErrorBody;
41
+
42
+ export interface CreatePageInput {
43
+ type: string;
44
+ slug: string;
45
+ page: Record<string, unknown>;
46
+ body: string;
47
+ }
48
+
49
+ /**
50
+ * ailkCreatePage handler.
51
+ *
52
+ * Optional `adapter` injection mirrors the Q3 resolution in the resource plan
53
+ * (tests construct adapters directly with isolated content roots). When
54
+ * omitted, falls back to the default MDX adapter rooted at process.cwd()/content.
55
+ */
56
+ export async function ailkCreatePage(
57
+ input: CreatePageInput,
58
+ adapter?: ContentAdapter,
59
+ ): Promise<CreatePageResult> {
60
+ const guard = await requirePaidTier();
61
+ if (guard) return guard;
62
+
63
+ const parsed = CreatePageInputSchema.safeParse({
64
+ ...input.page,
65
+ type: input.type,
66
+ slug: input.slug,
67
+ body: input.body,
68
+ });
69
+
70
+ if (!parsed.success) {
71
+ return validationError("Invalid frontmatter or body for create_page.", {
72
+ issues: parsed.error.issues,
73
+ });
74
+ }
75
+
76
+ const resolved =
77
+ adapter ??
78
+ createMdxAdapter({ contentRoot: join(process.cwd(), "content") });
79
+
80
+ try {
81
+ const result = await resolved.createPage(
82
+ parsed.data.type,
83
+ parsed.data.slug,
84
+ parsed.data,
85
+ );
86
+ return { page: result.page, version: result.version };
87
+ } catch (err) {
88
+ const mapped = mapAdapterError(err, {
89
+ type: parsed.data.type,
90
+ slug: parsed.data.slug,
91
+ });
92
+ if (mapped) return mapped;
93
+ throw err;
94
+ }
95
+ }