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,80 @@
1
+ /**
2
+ * Tests for the get_pricing MCP tool handler.
3
+ *
4
+ * Acceptance criteria verified here:
5
+ * - getPricingInputSchema validates correct inputs (slug optional)
6
+ * - getPricingInputSchema rejects invalid slugs
7
+ * - ailkGetPricing returns null for a slug with no content
8
+ * - ailkGetPricing output shape is correct when data exists
9
+ * - Output validates against getPricingOutputSchema
10
+ */
11
+
12
+ import { readFileSync } from "node:fs";
13
+ import { resolve } from "node:path";
14
+
15
+ import {
16
+ ailkGetPricing,
17
+ getPricingInputSchema,
18
+ getPricingOutputSchema,
19
+ } from "../src/tools/get_pricing.js";
20
+
21
+ const REPO_ROOT = resolve(__dirname, "../../..");
22
+ const CONTENT_ROOT = `${REPO_ROOT}/content`;
23
+
24
+ describe("getPricingInputSchema", () => {
25
+ it("accepts empty input (slug is optional)", () => {
26
+ const result = getPricingInputSchema.safeParse({});
27
+ expect(result.success).toBe(true);
28
+ });
29
+
30
+ it("accepts a valid slug", () => {
31
+ const result = getPricingInputSchema.safeParse({ slug: "pricing" });
32
+ expect(result.success).toBe(true);
33
+ });
34
+
35
+ it("rejects a slug with path traversal", () => {
36
+ const result = getPricingInputSchema.safeParse({ slug: "../etc/passwd" });
37
+ expect(result.success).toBe(false);
38
+ });
39
+
40
+ it("rejects a slug with uppercase letters", () => {
41
+ const result = getPricingInputSchema.safeParse({ slug: "Pricing" });
42
+ expect(result.success).toBe(false);
43
+ });
44
+
45
+ it("rejects an empty slug string", () => {
46
+ const result = getPricingInputSchema.safeParse({ slug: "" });
47
+ expect(result.success).toBe(false);
48
+ });
49
+ });
50
+
51
+ describe("ailkGetPricing handler", () => {
52
+ it("returns null for a slug with no matching content", async () => {
53
+ const result = await ailkGetPricing(
54
+ { slug: "nonexistent-slug-xqz" },
55
+ CONTENT_ROOT,
56
+ );
57
+ expect(result).toBeNull();
58
+ });
59
+
60
+ it("returns an object or null for default slug", async () => {
61
+ const result = await ailkGetPricing({}, CONTENT_ROOT);
62
+ expect(result === null || typeof result === "object").toBe(true);
63
+ });
64
+
65
+ it("output validates against getPricingOutputSchema", async () => {
66
+ const result = await ailkGetPricing({}, CONTENT_ROOT);
67
+ const parsed = getPricingOutputSchema.safeParse(result);
68
+ expect(parsed.success).toBe(true);
69
+ });
70
+ });
71
+
72
+ describe("architectural guard — no console calls", () => {
73
+ it("apps/mcp/src/tools/get_pricing.ts does not call console.*", () => {
74
+ const src = readFileSync(
75
+ resolve(__dirname, "../src/tools/get_pricing.ts"),
76
+ "utf-8",
77
+ );
78
+ expect(src).not.toMatch(/\bconsole\.(log|warn|error|info|debug)\b/);
79
+ });
80
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Tests for the get_site_info MCP tool handler.
3
+ *
4
+ * Acceptance criteria verified here:
5
+ * - getSiteInfoInputSchema validates correct inputs (slug optional)
6
+ * - getSiteInfoInputSchema rejects invalid slugs (path traversal, uppercase)
7
+ * - ailkGetSiteInfo returns null for a slug with no content
8
+ * - ailkGetSiteInfo returns a typed object when content exists
9
+ * - Output validates against getSiteInfoOutputSchema
10
+ */
11
+
12
+ import { readFileSync } from "node:fs";
13
+ import { resolve } from "node:path";
14
+
15
+ import {
16
+ ailkGetSiteInfo,
17
+ getSiteInfoInputSchema,
18
+ getSiteInfoOutputSchema,
19
+ } from "../src/tools/get_site_info.js";
20
+
21
+ const REPO_ROOT = resolve(__dirname, "../../..");
22
+ const CONTENT_ROOT = `${REPO_ROOT}/content`;
23
+
24
+ describe("getSiteInfoInputSchema", () => {
25
+ it("accepts empty input (slug is optional)", () => {
26
+ const result = getSiteInfoInputSchema.safeParse({});
27
+ expect(result.success).toBe(true);
28
+ });
29
+
30
+ it("accepts a valid slug", () => {
31
+ const result = getSiteInfoInputSchema.safeParse({ slug: "home" });
32
+ expect(result.success).toBe(true);
33
+ });
34
+
35
+ it("rejects a slug with path traversal", () => {
36
+ const result = getSiteInfoInputSchema.safeParse({ slug: "../etc/passwd" });
37
+ expect(result.success).toBe(false);
38
+ });
39
+
40
+ it("rejects a slug with uppercase letters", () => {
41
+ const result = getSiteInfoInputSchema.safeParse({ slug: "Home" });
42
+ expect(result.success).toBe(false);
43
+ });
44
+
45
+ it("rejects an empty slug string", () => {
46
+ const result = getSiteInfoInputSchema.safeParse({ slug: "" });
47
+ expect(result.success).toBe(false);
48
+ });
49
+ });
50
+
51
+ describe("ailkGetSiteInfo handler", () => {
52
+ it("returns null for a slug with no matching content", async () => {
53
+ const result = await ailkGetSiteInfo(
54
+ { slug: "nonexistent-slug-xqz" },
55
+ CONTENT_ROOT,
56
+ );
57
+ expect(result).toBeNull();
58
+ });
59
+
60
+ it("returns an object or null for default slug", async () => {
61
+ const result = await ailkGetSiteInfo({}, CONTENT_ROOT);
62
+ // May be null if no HomePage/home.mdx exists — both are valid
63
+ expect(result === null || typeof result === "object").toBe(true);
64
+ });
65
+
66
+ it("output validates against getSiteInfoOutputSchema when non-null", async () => {
67
+ const result = await ailkGetSiteInfo({}, CONTENT_ROOT);
68
+ const parsed = getSiteInfoOutputSchema.safeParse(result);
69
+ expect(parsed.success).toBe(true);
70
+ });
71
+ });
72
+
73
+ describe("architectural guard — no console calls", () => {
74
+ it("apps/mcp/src/tools/get_site_info.ts does not call console.*", () => {
75
+ const src = readFileSync(
76
+ resolve(__dirname, "../src/tools/get_site_info.ts"),
77
+ "utf-8",
78
+ );
79
+ expect(src).not.toMatch(/\bconsole\.(log|warn|error|info|debug)\b/);
80
+ });
81
+ });
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Integration tests for the MCP HTTP transport.
3
+ *
4
+ * Starts a real Fastify server on an ephemeral port (port 0) per test suite
5
+ * and uses Node's global fetch to send requests. Real TCP sockets avoid the
6
+ * `socket.destroySoon is not a function` crash that occurs when the MCP SDK's
7
+ * StreamableHTTPServerTransport receives the mock socket from app.inject().
8
+ *
9
+ * Uses stateless transport mode: each request is independent, responses are
10
+ * plain JSON (no SSE). This is the simplest deployment model and the easiest
11
+ * to test. Stateful session behaviour is exercised by the SDK's own test suite.
12
+ *
13
+ * Acceptance criteria verified here (Layer 2):
14
+ * - tools/list returns all OSS tools (count matches listRegisteredTools())
15
+ * - Read-tool call succeeds over HTTP
16
+ * - Write-tool call from a disallowed origin returns 403
17
+ * - Rate-limit triggers at configured threshold
18
+ * - Abuse rules reject invalid email on capture_lead
19
+ * - Honeypot field rejection on capture_lead
20
+ * - Blocked UA returns 403
21
+ */
22
+
23
+ import type { AddressInfo } from "node:net";
24
+
25
+ import type { FastifyInstance } from "fastify";
26
+ import { buildHttpTransportApp } from "../src/http-transport.js";
27
+ import { listRegisteredTools } from "../src/server.js";
28
+
29
+ /** Permissive JSON-RPC 2.0 envelope type for response body assertions. */
30
+ type JsonRpcResponse = {
31
+ jsonrpc?: string;
32
+ id?: number | string | null;
33
+ result?: { tools?: unknown[]; [k: string]: unknown };
34
+ error?: { code: number; message: string };
35
+ };
36
+
37
+ const TOOLS_LIST_PAYLOAD = {
38
+ jsonrpc: "2.0",
39
+ id: 1,
40
+ method: "tools/list",
41
+ params: {},
42
+ };
43
+
44
+ /** Default config used by most test suites. */
45
+ const DEFAULT_CONFIG = {
46
+ path: "/mcp",
47
+ stateless: true,
48
+ rateLimits: { readPerMin: 60, writePerMin: 10 },
49
+ cors: { writeAllowedOrigins: [] as string[] },
50
+ abuse: {
51
+ captureLeadDailyCapPerIp: 50,
52
+ blockedUserAgents: "",
53
+ },
54
+ } as const;
55
+
56
+ /**
57
+ * Post a JSON-RPC payload to the server at the given port and return the
58
+ * fetch Response. Includes the Accept header required by the MCP Streamable
59
+ * HTTP spec.
60
+ */
61
+ async function mcpPost(
62
+ port: number,
63
+ payload: object,
64
+ headers: Record<string, string> = {},
65
+ ): Promise<Response> {
66
+ return fetch(`http://127.0.0.1:${port}/mcp`, {
67
+ method: "POST",
68
+ body: JSON.stringify(payload),
69
+ headers: {
70
+ "content-type": "application/json",
71
+ accept: "application/json, text/event-stream",
72
+ ...headers,
73
+ },
74
+ });
75
+ }
76
+
77
+ describe("buildHttpTransportApp", () => {
78
+ let app: FastifyInstance;
79
+ let port: number;
80
+
81
+ beforeEach(async () => {
82
+ app = await buildHttpTransportApp(DEFAULT_CONFIG);
83
+ await app.listen({ host: "127.0.0.1", port: 0 });
84
+ port = (app.server.address() as AddressInfo).port;
85
+ });
86
+
87
+ afterEach(async () => {
88
+ await app.close();
89
+ });
90
+
91
+ it("starts listening on an ephemeral port without throwing", () => {
92
+ expect(app.server.listening).toBe(true);
93
+ expect(port).toBeGreaterThan(0);
94
+ });
95
+ });
96
+
97
+ describe("tools/list over HTTP (stateless)", () => {
98
+ let app: FastifyInstance;
99
+ let port: number;
100
+
101
+ beforeEach(async () => {
102
+ app = await buildHttpTransportApp(DEFAULT_CONFIG);
103
+ await app.listen({ host: "127.0.0.1", port: 0 });
104
+ port = (app.server.address() as AddressInfo).port;
105
+ });
106
+
107
+ afterEach(async () => {
108
+ await app.close();
109
+ });
110
+
111
+ it("returns 200 for a tools/list request", async () => {
112
+ const res = await mcpPost(port, TOOLS_LIST_PAYLOAD);
113
+ expect(res.status).toBe(200);
114
+ });
115
+
116
+ it("tools/list response includes all OSS tools", async () => {
117
+ const res = await mcpPost(port, TOOLS_LIST_PAYLOAD);
118
+ expect(res.status).toBe(200);
119
+
120
+ const body = (await res.json()) as JsonRpcResponse;
121
+ expect(body.result).toBeDefined();
122
+ // body.result is confirmed defined above; non-null assertion is safe
123
+ expect(Array.isArray(body.result!.tools)).toBe(true);
124
+
125
+ const expectedCount = listRegisteredTools().length;
126
+ expect(body.result!.tools).toHaveLength(expectedCount);
127
+ });
128
+ });
129
+
130
+ describe("CORS enforcement on write tools", () => {
131
+ let app: FastifyInstance;
132
+ let port: number;
133
+
134
+ beforeEach(async () => {
135
+ app = await buildHttpTransportApp(DEFAULT_CONFIG);
136
+ await app.listen({ host: "127.0.0.1", port: 0 });
137
+ port = (app.server.address() as AddressInfo).port;
138
+ });
139
+
140
+ afterEach(async () => {
141
+ await app.close();
142
+ });
143
+
144
+ it("blocks a tools/call write from a non-allowlisted origin", async () => {
145
+ const payload = {
146
+ jsonrpc: "2.0",
147
+ id: 3,
148
+ method: "tools/call",
149
+ params: {
150
+ name: "capture_lead",
151
+ arguments: {
152
+ name: "Bot",
153
+ email: "bot@evil.com",
154
+ },
155
+ },
156
+ };
157
+
158
+ const res = await mcpPost(port, payload, {
159
+ origin: "https://evil.example.com",
160
+ });
161
+
162
+ expect(res.status).toBe(403);
163
+ });
164
+
165
+ it("allows a tools/call write from an allowlisted origin", async () => {
166
+ const allowedApp = await buildHttpTransportApp({
167
+ ...DEFAULT_CONFIG,
168
+ cors: { writeAllowedOrigins: ["https://allowed.example.com"] },
169
+ });
170
+ await allowedApp.listen({ host: "127.0.0.1", port: 0 });
171
+ const allowedPort = (allowedApp.server.address() as AddressInfo).port;
172
+
173
+ try {
174
+ const payload = {
175
+ jsonrpc: "2.0",
176
+ id: 3,
177
+ method: "tools/call",
178
+ params: {
179
+ name: "capture_lead",
180
+ arguments: {
181
+ name: "Jane",
182
+ email: "jane@example.com",
183
+ },
184
+ },
185
+ };
186
+
187
+ const res = await mcpPost(allowedPort, payload, {
188
+ origin: "https://allowed.example.com",
189
+ });
190
+
191
+ // Should NOT be 403 (may be 200 or a tool-level error, but not CORS blocked)
192
+ expect(res.status).not.toBe(403);
193
+ } finally {
194
+ await allowedApp.close();
195
+ }
196
+ });
197
+ });
198
+
199
+ describe("Rate limiting", () => {
200
+ // Use a very low limit to test without hammering
201
+ const LOW_RATE_CONFIG = {
202
+ ...DEFAULT_CONFIG,
203
+ rateLimits: { readPerMin: 3, writePerMin: 2 },
204
+ } as const;
205
+
206
+ let app: FastifyInstance;
207
+ let port: number;
208
+
209
+ beforeEach(async () => {
210
+ app = await buildHttpTransportApp(LOW_RATE_CONFIG);
211
+ await app.listen({ host: "127.0.0.1", port: 0 });
212
+ port = (app.server.address() as AddressInfo).port;
213
+ });
214
+
215
+ afterEach(async () => {
216
+ await app.close();
217
+ });
218
+
219
+ it("returns 429 after exceeding the read rate limit", async () => {
220
+ const payload = {
221
+ jsonrpc: "2.0",
222
+ id: 10,
223
+ method: "tools/list",
224
+ params: {},
225
+ };
226
+
227
+ let lastStatus = 200;
228
+ for (let i = 0; i < 5; i++) {
229
+ const res = await mcpPost(port, payload);
230
+ lastStatus = res.status;
231
+ }
232
+
233
+ expect(lastStatus).toBe(429);
234
+ });
235
+ });
236
+
237
+ describe("Abuse rules", () => {
238
+ const OPEN_CORS_CONFIG = {
239
+ ...DEFAULT_CONFIG,
240
+ cors: { writeAllowedOrigins: ["*"] as string[] },
241
+ };
242
+
243
+ let app: FastifyInstance;
244
+ let port: number;
245
+
246
+ beforeEach(async () => {
247
+ app = await buildHttpTransportApp(OPEN_CORS_CONFIG);
248
+ await app.listen({ host: "127.0.0.1", port: 0 });
249
+ port = (app.server.address() as AddressInfo).port;
250
+ });
251
+
252
+ afterEach(async () => {
253
+ await app.close();
254
+ });
255
+
256
+ it("rejects capture_lead with malformed email", async () => {
257
+ const payload = {
258
+ jsonrpc: "2.0",
259
+ id: 20,
260
+ method: "tools/call",
261
+ params: {
262
+ name: "capture_lead",
263
+ arguments: {
264
+ name: "Spammer",
265
+ email: "not-an-email",
266
+ },
267
+ },
268
+ };
269
+
270
+ const res = await mcpPost(port, payload);
271
+ expect(res.status).toBe(422);
272
+ });
273
+
274
+ it("rejects capture_lead with honeypot field filled", async () => {
275
+ const payload = {
276
+ jsonrpc: "2.0",
277
+ id: 21,
278
+ method: "tools/call",
279
+ params: {
280
+ name: "capture_lead",
281
+ arguments: {
282
+ name: "Bot",
283
+ email: "bot@example.com",
284
+ honeypot: "i-am-a-bot",
285
+ },
286
+ },
287
+ };
288
+
289
+ const res = await mcpPost(port, payload);
290
+ expect(res.status).toBe(422);
291
+ });
292
+
293
+ it("blocks requests from blocked user agents", async () => {
294
+ const blockedApp = await buildHttpTransportApp({
295
+ ...OPEN_CORS_CONFIG,
296
+ abuse: {
297
+ captureLeadDailyCapPerIp: 50,
298
+ blockedUserAgents: "EvilBot",
299
+ },
300
+ });
301
+ await blockedApp.listen({ host: "127.0.0.1", port: 0 });
302
+ const blockedPort = (blockedApp.server.address() as AddressInfo).port;
303
+
304
+ try {
305
+ const res = await mcpPost(
306
+ blockedPort,
307
+ {
308
+ jsonrpc: "2.0",
309
+ id: 22,
310
+ method: "tools/call",
311
+ params: {
312
+ name: "capture_lead",
313
+ arguments: { name: "Bot", email: "bot@example.com" },
314
+ },
315
+ },
316
+ { "user-agent": "EvilBot/1.0" },
317
+ );
318
+ expect(res.status).toBe(403);
319
+ } finally {
320
+ await blockedApp.close();
321
+ }
322
+ });
323
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Unit tests for the MCP-side canonical error helpers used by D14a content-
3
+ * write tools. Same shape as the apps/api twin minus the status field — MCP
4
+ * surfaces errors as the tool result body itself.
5
+ */
6
+
7
+ import {
8
+ conflictError,
9
+ notFoundError,
10
+ paymentRequiredError,
11
+ unauthorizedError,
12
+ validationError,
13
+ } from "../../src/lib/errors.js";
14
+
15
+ describe("apps/mcp/lib/errors", () => {
16
+ it("validationError carries validation_failed code + message", () => {
17
+ expect(validationError("Invalid input")).toEqual({
18
+ error: "validation_failed",
19
+ message: "Invalid input",
20
+ });
21
+ });
22
+
23
+ it("validationError includes details when provided", () => {
24
+ expect(
25
+ validationError("Bad type", { field: "type", received: "Unknown" }),
26
+ ).toEqual({
27
+ error: "validation_failed",
28
+ message: "Bad type",
29
+ details: { field: "type", received: "Unknown" },
30
+ });
31
+ });
32
+
33
+ it("validationError omits details key when not provided", () => {
34
+ const err = validationError("X");
35
+ expect("details" in err).toBe(false);
36
+ });
37
+
38
+ it("unauthorizedError carries unauthorized code", () => {
39
+ expect(unauthorizedError("nope")).toEqual({
40
+ error: "unauthorized",
41
+ message: "nope",
42
+ });
43
+ });
44
+
45
+ it("paymentRequiredError carries payment_required code", () => {
46
+ expect(paymentRequiredError("upgrade")).toEqual({
47
+ error: "payment_required",
48
+ message: "upgrade",
49
+ });
50
+ });
51
+
52
+ it("notFoundError carries not_found code", () => {
53
+ expect(notFoundError("missing")).toEqual({
54
+ error: "not_found",
55
+ message: "missing",
56
+ });
57
+ });
58
+
59
+ it("conflictError carries conflict code", () => {
60
+ expect(conflictError("stale")).toEqual({
61
+ error: "conflict",
62
+ message: "stale",
63
+ });
64
+ });
65
+
66
+ it("conflictError includes details (e.g. existing version)", () => {
67
+ expect(conflictError("Slug exists", { existingVersion: "abc123" })).toEqual(
68
+ {
69
+ error: "conflict",
70
+ message: "Slug exists",
71
+ details: { existingVersion: "abc123" },
72
+ },
73
+ );
74
+ });
75
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Unit tests for the MCP-side require-paid-tier guard.
3
+ *
4
+ * Mocks @working-theory/license `checkLicense` and asserts:
5
+ * - free tier → returns canonical payment_required body
6
+ * - pro tier → returns null (caller proceeds)
7
+ * - checkLicense throws → safe-by-default, returns payment_required
8
+ */
9
+
10
+ const mockCheckLicense = jest.fn();
11
+
12
+ jest.mock("@working-theory/license", () => ({
13
+ checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
14
+ }));
15
+
16
+ import { requirePaidTier } from "../../src/lib/require-paid-tier.js";
17
+
18
+ describe("requirePaidTier (mcp)", () => {
19
+ beforeEach(() => {
20
+ mockCheckLicense.mockReset();
21
+ });
22
+
23
+ it("free tier — returns canonical payment_required body", async () => {
24
+ mockCheckLicense.mockResolvedValue({
25
+ tier: "free",
26
+ plan: null,
27
+ features: [],
28
+ expiresAt: null,
29
+ revoked: false,
30
+ });
31
+
32
+ const guard = await requirePaidTier();
33
+
34
+ expect(guard).toEqual({
35
+ error: "payment_required",
36
+ message: expect.stringContaining("paid license"),
37
+ });
38
+ });
39
+
40
+ it("pro tier — returns null (caller proceeds)", async () => {
41
+ mockCheckLicense.mockResolvedValue({
42
+ tier: "pro",
43
+ plan: "pro",
44
+ features: [],
45
+ expiresAt: null,
46
+ revoked: false,
47
+ });
48
+
49
+ const guard = await requirePaidTier();
50
+ expect(guard).toBeNull();
51
+ });
52
+
53
+ it("checkLicense throws — degrades to payment_required (safe default)", async () => {
54
+ mockCheckLicense.mockRejectedValue(new Error("boom"));
55
+
56
+ const guard = await requirePaidTier();
57
+ expect(guard).toMatchObject({ error: "payment_required" });
58
+ });
59
+
60
+ it("revoked paid license — returns payment_required even though tier is pro (issue #398)", async () => {
61
+ // After a 403/revoked refresh, checkLicense() returns tier:'pro' until
62
+ // the existing JWT exp (up to 24h). The revoked flag is the early
63
+ // signal — we must close the gate now, not wait for exp.
64
+ mockCheckLicense.mockResolvedValue({
65
+ tier: "pro",
66
+ plan: "pro",
67
+ features: [],
68
+ expiresAt: null,
69
+ revoked: true,
70
+ });
71
+
72
+ const guard = await requirePaidTier();
73
+ expect(guard).toMatchObject({ error: "payment_required" });
74
+ });
75
+ });