create-ailk 0.4.0 → 0.6.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 (396) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +12248 -0
  3. package/component-catalog.md +193 -68
  4. package/dist/cli.js +29 -1
  5. package/dist/derive-architecture.js +5 -2
  6. package/dist/lib/api-boot-assertion.d.ts +46 -0
  7. package/dist/lib/api-boot-assertion.js +70 -0
  8. package/dist/lib/extract-module-bundle.d.ts +50 -2
  9. package/dist/lib/extract-module-bundle.js +223 -15
  10. package/dist/lib/fetch-guard.d.ts +28 -0
  11. package/dist/lib/fetch-guard.js +101 -0
  12. package/dist/lib/fetch-module.js +4 -0
  13. package/dist/lib/fetch-template.js +4 -0
  14. package/dist/lib/import-graph.d.ts +29 -0
  15. package/dist/lib/import-graph.js +140 -0
  16. package/dist/module-architecture.d.ts +60 -8
  17. package/dist/module-architecture.js +587 -242
  18. package/dist/parse-args.js +6 -2
  19. package/dist/programmatic.d.ts +10 -0
  20. package/dist/programmatic.js +134 -19
  21. package/dist/site-chrome.d.ts +80 -0
  22. package/dist/site-chrome.js +135 -0
  23. package/dist/surfaces.d.ts +21 -6
  24. package/dist/surfaces.js +209 -36
  25. package/dist/template-manifest.d.ts +88 -3
  26. package/dist/template-manifest.js +281 -29
  27. package/dist/template-names.d.ts +1 -1
  28. package/dist/template-names.js +2 -0
  29. package/package.json +10 -2
  30. package/site-templates/_base/ailk-template.json +6 -0
  31. package/site-templates/_base/content/en/Pricing/pricing.mdx +169 -0
  32. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +351 -0
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +400 -0
  34. package/site-templates/marketing-site/ailk-template.json +11 -0
  35. package/site-templates/marketing-site/content/en/blog/05-this-vc-morning-routine-will-make-you-super-annoying.mdx +36 -0
  36. package/site-templates/marketing-site/provenance.json +4 -0
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +288 -0
  38. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +286 -0
  39. package/site-templates/marketing-site-free/ailk-template.json +2 -1
  40. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +279 -0
  41. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +336 -0
  42. package/site-templates/minimalism-site/ailk-template.json +22 -0
  43. package/site-templates/minimalism-site/assets/avatar-placeholder-1.svg +7 -0
  44. package/site-templates/minimalism-site/assets/avatar-placeholder-2.svg +7 -0
  45. package/site-templates/minimalism-site/assets/avatar-placeholder-3.svg +7 -0
  46. package/site-templates/minimalism-site/assets/avatar-placeholder-4.svg +7 -0
  47. package/site-templates/minimalism-site/assets/avatar-placeholder-5.svg +7 -0
  48. package/site-templates/minimalism-site/assets/avatar-placeholder-6.svg +7 -0
  49. package/site-templates/minimalism-site/assets/hero-backdrop.svg +17 -0
  50. package/site-templates/minimalism-site/assets/logo-placeholder-1.svg +6 -0
  51. package/site-templates/minimalism-site/assets/logo-placeholder-2.svg +6 -0
  52. package/site-templates/minimalism-site/assets/logo-placeholder-3.svg +6 -0
  53. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  54. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +110 -0
  55. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +68 -0
  56. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +57 -0
  57. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +244 -0
  58. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +77 -0
  59. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +73 -0
  60. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +52 -0
  61. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +52 -0
  62. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +52 -0
  63. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +52 -0
  64. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +38 -0
  65. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +38 -0
  66. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +38 -0
  67. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +73 -0
  68. package/site-templates/minimalism-site/flows/referral.flow.json +71 -0
  69. package/site-templates/minimalism-site/provenance.json +4 -0
  70. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +61 -0
  71. package/site-templates/saas-product/ailk-template.json +16 -0
  72. package/site-templates/saas-product/provenance.json +10 -0
  73. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  74. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  75. package/site-templates/simple-service/ailk-template.json +10 -0
  76. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  77. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  78. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  79. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  80. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  81. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  82. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  83. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  84. package/site-templates/simple-service/provenance.json +4 -0
  85. package/templates/.claude/skills/README.md +16 -25
  86. package/templates/.env.example +102 -12
  87. package/templates/.github/workflows/ci.yml +48 -0
  88. package/templates/README.md +3 -1
  89. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  90. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  91. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  92. package/templates/apps/api/.env.example +13 -0
  93. package/templates/apps/api/CLAUDE.md +29 -2
  94. package/templates/apps/api/eslint.config.mjs +69 -0
  95. package/templates/apps/api/jest.config.cjs +3 -0
  96. package/templates/apps/api/package.json +7 -2
  97. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  98. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +89 -9
  99. package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +167 -0
  100. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  101. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  102. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +10 -1
  103. package/templates/apps/api/src/config/index.ts +62 -0
  104. package/templates/apps/api/src/config/modules.ts +7 -3
  105. package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -2
  106. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  107. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  108. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  109. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  110. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  111. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  112. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  113. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  114. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  115. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  116. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  117. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  118. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  119. package/templates/apps/api/src/lib/site.ts +208 -9
  120. package/templates/apps/api/src/lib/stripe.ts +66 -0
  121. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +521 -0
  122. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +150 -0
  123. package/templates/apps/api/src/middleware/default-tenant.ts +136 -0
  124. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  125. package/templates/apps/api/src/openapi/spec.ts +543 -2
  126. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  127. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  128. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  129. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  130. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  131. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  132. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  133. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  134. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  135. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  136. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  137. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  138. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  139. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  140. package/templates/apps/api/src/routes/content/index.ts +13 -73
  141. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  142. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  143. package/templates/apps/api/src/routes/content/search.ts +129 -0
  144. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  145. package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +171 -0
  146. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  147. package/templates/apps/api/src/routes/leads/list.ts +104 -0
  148. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  149. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  150. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  151. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  152. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  153. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  154. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  155. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  156. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  157. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  158. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  159. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  160. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  161. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  162. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  163. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  164. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  165. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  166. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  167. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  168. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  169. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  170. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  171. package/templates/apps/api/src/server.ts +521 -55
  172. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  173. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  174. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  175. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  176. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  177. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  178. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  179. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +408 -0
  180. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  181. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +329 -0
  182. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  183. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  184. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  185. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  186. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  187. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  188. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  189. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  190. package/templates/apps/api/src/services/audit-log.ts +668 -0
  191. package/templates/apps/api/src/services/consent.ts +18 -0
  192. package/templates/apps/api/src/services/default-tenant.ts +300 -0
  193. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  194. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  195. package/templates/apps/api/src/services/entitlements.ts +80 -0
  196. package/templates/apps/api/src/services/lead-promotion.ts +160 -0
  197. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  198. package/templates/apps/api/src/services/leads-inbox.ts +284 -0
  199. package/templates/apps/api/src/services/listing-config.ts +27 -9
  200. package/templates/apps/api/src/services/owner-workspaces.ts +132 -0
  201. package/templates/apps/api/src/services/project-listings.ts +201 -6
  202. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  203. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  204. package/templates/apps/api/src/services/revalidation.ts +173 -0
  205. package/templates/apps/api/src/services/verified-email.ts +71 -0
  206. package/templates/apps/api/src/vercel-handler.ts +75 -35
  207. package/templates/apps/mcp/.env.example +17 -0
  208. package/templates/apps/mcp/CLAUDE.md +58 -25
  209. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  210. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  211. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  212. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  213. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  214. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  215. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  216. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  217. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  218. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  219. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  220. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  221. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  222. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  223. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  224. package/templates/apps/mcp/__tests__/parity.test.ts +12 -9
  225. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  226. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  227. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  228. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  229. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  230. package/templates/apps/mcp/__tests__/tools/{scaffold.test.ts → create_project.test.ts} +23 -23
  231. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  232. package/templates/apps/mcp/eslint.config.mjs +69 -0
  233. package/templates/apps/mcp/jest.config.cjs +13 -0
  234. package/templates/apps/mcp/package.json +8 -2
  235. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  236. package/templates/apps/mcp/src/config/modules.ts +10 -5
  237. package/templates/apps/mcp/src/http-transport.ts +102 -29
  238. package/templates/apps/mcp/src/index.ts +73 -11
  239. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  240. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  241. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  242. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  243. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  244. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  245. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  246. package/templates/apps/mcp/src/server.ts +61 -37
  247. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  248. package/templates/apps/mcp/src/tools/{scaffold.ts → create_project.ts} +3 -3
  249. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  250. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  251. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  252. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  253. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  254. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  255. package/templates/apps/mcp/src/tools/index.ts +9 -7
  256. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  257. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  258. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  259. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  260. package/templates/apps/web/.env.example +74 -8
  261. package/templates/apps/web/CLAUDE.md +46 -0
  262. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  263. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  264. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  265. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  266. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  267. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  268. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  269. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  270. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  271. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  272. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  273. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  274. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  275. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  276. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  277. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  278. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  279. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  280. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  281. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +67 -14
  282. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -2
  283. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +181 -0
  284. package/templates/apps/web/app/[locale]/(authed)/leads/components/LeadsTable.tsx +137 -0
  285. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +111 -0
  286. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +1 -1
  287. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +111 -0
  288. package/templates/apps/web/app/[locale]/[slug]/page.tsx +65 -0
  289. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  290. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +1 -1
  291. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +1 -1
  292. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +6 -5
  293. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +1 -1
  294. package/templates/apps/web/app/[locale]/layout.tsx +308 -65
  295. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  296. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  297. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  298. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  299. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  300. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  301. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  302. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  303. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  304. package/templates/apps/web/auth.ts +407 -8
  305. package/templates/apps/web/components/google-analytics.tsx +18 -1
  306. package/templates/apps/web/eslint.config.mjs +69 -0
  307. package/templates/apps/web/jest.config.cjs +15 -19
  308. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  309. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  310. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  311. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +50 -6
  312. package/templates/apps/web/lib/auth-email.ts +318 -0
  313. package/templates/apps/web/lib/auth-env.ts +85 -0
  314. package/templates/apps/web/lib/auth-routes.ts +55 -0
  315. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  316. package/templates/apps/web/lib/nav-config.ts +27 -7
  317. package/templates/apps/web/lib/server-api.ts +1 -1
  318. package/templates/apps/web/lib/site-chrome.ts +29 -0
  319. package/templates/apps/web/lib/telemetry-scrub.ts +50 -0
  320. package/templates/apps/web/messages/en.json +216 -2
  321. package/templates/apps/web/next.config.mjs +20 -0
  322. package/templates/apps/web/package.json +8 -3
  323. package/templates/apps/web/sentry.client.config.ts +131 -0
  324. package/templates/apps/web/sentry.edge.config.ts +72 -0
  325. package/templates/apps/web/sentry.server.config.ts +72 -0
  326. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  327. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  328. package/templates/apps/web/src/lib/routes.ts +13 -18
  329. package/templates/brand_assets/README.md +24 -0
  330. package/templates/content/en/AboutPage/about.mdx +160 -15
  331. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  332. package/templates/content/en/ContactPage/contact.mdx +90 -13
  333. package/templates/content/en/FAQPage/faq.mdx +62 -22
  334. package/templates/content/en/HomePage/home.mdx +244 -20
  335. package/templates/content/en/LandingPage/blog.mdx +83 -0
  336. package/templates/content/en/LandingPage/features.mdx +103 -0
  337. package/templates/content/en/LandingPage/how-it-works.mdx +143 -0
  338. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  339. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  340. package/templates/database/CHANGELOG.md +149 -0
  341. package/templates/database/README.md +10 -3
  342. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  343. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  344. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  345. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  346. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  347. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  348. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  349. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  350. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  351. package/templates/database/__tests__/seed.test.ts +1 -1
  352. package/templates/database/__tests__/tenancy.test.ts +141 -0
  353. package/templates/database/config/schema.prisma +79 -2
  354. package/templates/database/content/schema.prisma +87 -11
  355. package/templates/database/eslint.config.mjs +69 -0
  356. package/templates/database/inbox/schema.prisma +452 -9
  357. package/templates/database/jest.config.cjs +5 -3
  358. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  359. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  360. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  361. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  362. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  363. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  364. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  365. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  366. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  367. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  368. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  369. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  370. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  371. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  372. package/templates/database/package.json +7 -3
  373. package/templates/database/seed.ts +1 -1
  374. package/templates/database/tenancy.ts +86 -1
  375. package/templates/database/tsconfig.json +1 -0
  376. package/templates/package.json +1 -14
  377. package/versions.json +52 -0
  378. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -103
  379. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  380. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  381. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  382. package/templates/apps/api/src/routes/content/create.ts +0 -115
  383. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  384. package/templates/apps/api/src/routes/content/update.ts +0 -112
  385. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  386. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  387. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  388. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  389. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  390. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  391. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  392. /package/site-templates/{marketing-site-free → _base}/content/en/PrivacyPage/privacy.mdx +0 -0
  393. /package/site-templates/{marketing-site-free → _base}/content/en/TermsPage/terms.mdx +0 -0
  394. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  395. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
  396. /package/templates/apps/web/{src/components → components}/SiteNav.tsx +0 -0
@@ -1,53 +1,99 @@
1
1
  /**
2
- * Tests for the ailkPurchase MCP tool (Phase 3 U2: live implementation).
2
+ * Tests for the ailkPurchase MCP tool.
3
+ *
4
+ * Rewritten for #6001: three security reviews on PR #5991 flagged that an
5
+ * anonymous MCP caller could forward its own successUrl/cancelUrl/
6
+ * allowPromotionCodes to `checkout.createSession` — a phishing-amplification
7
+ * open redirect off the seller's real checkout — so the tool is now gated by
8
+ * `purchaseMcpInputSchema`, a `.strict()` `.pick()` of the shared
9
+ * `purchaseInputSchema` exposing exactly `productCode` + `quantity`. A call
10
+ * carrying any of the three excluded fields now FAILS validation rather than
11
+ * having them silently stripped, and both redirects are ALWAYS derived from
12
+ * `AILK_BASE_URL`.
13
+ *
14
+ * `purchaseInputSchema` itself is unchanged (#5971 shape, still shared with
15
+ * the Server Action / API route, which are not anonymous surfaces) — its own
16
+ * schema tests below stay as coverage of the SHARED schema, distinct from
17
+ * `purchaseMcpInputSchema`'s tests, which cover the tool's OWN gate.
18
+ *
19
+ * `__tests__/start_purchase.test.ts` owns the parity proof and the
20
+ * hosted-URL/unknown-offering behaviour; this suite keeps the schema coverage
21
+ * and the architectural guard alongside it.
3
22
  *
4
23
  * Acceptance criteria verified here:
5
- * - purchaseInputSchema validates correct inputs
6
- * - purchaseInputSchema rejects invalid inputs
7
- * - purchaseOutputSchema validates { url: string } live shape
8
- * - ailkPurchase handler calls POST /v1/checkout/sessions
9
- * - ailkPurchase returns { url: string } on success
10
- * - ailkPurchase returns Zod errors on invalid input
24
+ * - purchaseInputSchema (the shared schema) accepts/rejects per the route's
25
+ * own validation — unchanged coverage, since the schema itself is
26
+ * byte-unchanged (#6001 AC3)
27
+ * - purchaseMcpInputSchema (the tool's own gate) accepts exactly
28
+ * productCode + quantity and REJECTS successUrl/cancelUrl/
29
+ * allowPromotionCodes outright (#6001 AC1)
30
+ * - purchaseOutputSchema validates the { url: string } live shape
31
+ * - ailkPurchase calls POST /v1/checkout/sessions through the api-client seam
32
+ * - ailkPurchase always derives successUrl/cancelUrl from AILK_BASE_URL, with
33
+ * and without the env var set, and never forwards allowPromotionCodes
34
+ * (#6001 AC2)
35
+ * - ailkPurchase returns { url } on success, a typed body on bad input
11
36
  * - No Stripe SDK import in apps/mcp (architectural guard)
12
- * - Parity: response shape matches purchaseAction Server Action shape
13
37
  */
14
38
 
15
- import { ailkPurchase } from "../../src/tools/purchase.js";
39
+ import { checkout } from "@working-theory/api-client";
16
40
  import {
17
41
  purchaseInputSchema as PurchaseInputSchema,
42
+ purchaseMcpInputSchema as PurchaseMcpInputSchema,
18
43
  purchaseOutputSchema as PurchaseOutputSchema,
19
44
  } from "@working-theory/validation";
20
45
 
21
- // ── fetch mock ────────────────────────────────────────────────────────────────
46
+ import {
47
+ __setMcpCommerceClaimForTest,
48
+ resetMcpCommerceClaim,
49
+ } from "../../src/lib/commerce-claim.js";
50
+ import { ailkPurchase } from "../../src/tools/purchase.js";
51
+
52
+ jest.mock("@working-theory/api-client", () => ({
53
+ checkout: { createSession: jest.fn() },
54
+ }));
55
+
56
+ const createSession = checkout.createSession as jest.MockedFunction<
57
+ typeof checkout.createSession
58
+ >;
22
59
 
23
- const mockFetch = jest.fn();
24
- global.fetch = mockFetch;
60
+ function sessionCreated(url: string) {
61
+ return { ok: true as const, body: { sessionId: "cs_test_id", url } };
62
+ }
63
+
64
+ beforeEach(() => {
65
+ // Every direct `ailkPurchase` call below bypasses registration, so gate 2
66
+ // would answer the module-disabled body under the fail-closed default.
67
+ __setMcpCommerceClaimForTest("claimed");
68
+ });
25
69
 
26
70
  afterEach(() => {
27
71
  jest.clearAllMocks();
72
+ resetMcpCommerceClaim();
73
+ delete process.env.AILK_BASE_URL;
28
74
  });
29
75
 
30
- // ── Input schema ──────────────────────────────────────────────────────────────
76
+ // ── Shared input schema (purchaseInputSchema — byte-unchanged, #6001 AC3) ──────
31
77
 
32
- describe("PurchaseInputSchema", () => {
33
- it("accepts valid input with priceId and quantity", () => {
78
+ describe("PurchaseInputSchema — the shared schema (Server Action / API route)", () => {
79
+ it("accepts valid input with productCode and quantity", () => {
34
80
  const result = PurchaseInputSchema.safeParse({
35
- priceId: "price_test_123",
81
+ productCode: "price_test_123",
36
82
  quantity: 1,
37
83
  });
38
84
  expect(result.success).toBe(true);
39
85
  });
40
86
 
41
- it("accepts valid input with priceId only (quantity defaults)", () => {
87
+ it("accepts valid input with productCode only (quantity defaults)", () => {
42
88
  const result = PurchaseInputSchema.safeParse({
43
- priceId: "price_test_abc",
89
+ productCode: "price_test_abc",
44
90
  });
45
91
  expect(result.success).toBe(true);
46
92
  });
47
93
 
48
94
  it("accepts valid input with successUrl and cancelUrl", () => {
49
95
  const result = PurchaseInputSchema.safeParse({
50
- priceId: "price_test_123",
96
+ productCode: "price_test_123",
51
97
  successUrl: "https://example.com/success",
52
98
  cancelUrl: "https://example.com/cancel",
53
99
  });
@@ -56,7 +102,7 @@ describe("PurchaseInputSchema", () => {
56
102
 
57
103
  it("accepts an optional allowPromotionCodes boolean", () => {
58
104
  const result = PurchaseInputSchema.safeParse({
59
- priceId: "price_test_123",
105
+ productCode: "price_test_123",
60
106
  allowPromotionCodes: true,
61
107
  });
62
108
  expect(result.success).toBe(true);
@@ -64,25 +110,39 @@ describe("PurchaseInputSchema", () => {
64
110
 
65
111
  it("rejects a non-boolean allowPromotionCodes", () => {
66
112
  const result = PurchaseInputSchema.safeParse({
67
- priceId: "price_test_123",
113
+ productCode: "price_test_123",
68
114
  allowPromotionCodes: "yes",
69
115
  });
70
116
  expect(result.success).toBe(false);
71
117
  });
72
118
 
73
- it("rejects input missing priceId", () => {
119
+ it("rejects input missing productCode", () => {
74
120
  const result = PurchaseInputSchema.safeParse({ quantity: 1 });
75
121
  expect(result.success).toBe(false);
76
122
  });
77
123
 
78
- it("rejects input with empty priceId", () => {
79
- const result = PurchaseInputSchema.safeParse({ priceId: "" });
124
+ it("rejects input with empty productCode", () => {
125
+ const result = PurchaseInputSchema.safeParse({ productCode: "" });
126
+ expect(result.success).toBe(false);
127
+ });
128
+
129
+ it("rejects a productCode outside the route's character set", () => {
130
+ const result = PurchaseInputSchema.safeParse({
131
+ productCode: "has spaces",
132
+ });
133
+ expect(result.success).toBe(false);
134
+ });
135
+
136
+ it("rejects a productCode longer than 200 characters", () => {
137
+ const result = PurchaseInputSchema.safeParse({
138
+ productCode: "a".repeat(201),
139
+ });
80
140
  expect(result.success).toBe(false);
81
141
  });
82
142
 
83
143
  it("rejects input with non-integer quantity", () => {
84
144
  const result = PurchaseInputSchema.safeParse({
85
- priceId: "price_x",
145
+ productCode: "price_x",
86
146
  quantity: 1.5,
87
147
  });
88
148
  expect(result.success).toBe(false);
@@ -90,11 +150,98 @@ describe("PurchaseInputSchema", () => {
90
150
 
91
151
  it("rejects input with quantity less than 1", () => {
92
152
  const result = PurchaseInputSchema.safeParse({
93
- priceId: "price_x",
153
+ productCode: "price_x",
94
154
  quantity: 0,
95
155
  });
96
156
  expect(result.success).toBe(false);
97
157
  });
158
+
159
+ it("rejects input with quantity above the route's maximum of 999", () => {
160
+ const result = PurchaseInputSchema.safeParse({
161
+ productCode: "price_x",
162
+ quantity: 1000,
163
+ });
164
+ expect(result.success).toBe(false);
165
+ });
166
+
167
+ it("rejects a non-http(s) redirect URL the route would reject", () => {
168
+ const result = PurchaseInputSchema.safeParse({
169
+ productCode: "price_x",
170
+ successUrl: "javascript:alert(1)",
171
+ });
172
+ expect(result.success).toBe(false);
173
+ });
174
+ });
175
+
176
+ // ── MCP-only input schema (purchaseMcpInputSchema — the tool's own gate) ───────
177
+
178
+ describe("PurchaseMcpInputSchema — the registered tool's OWN gate (#6001)", () => {
179
+ it("accepts productCode and quantity", () => {
180
+ const result = PurchaseMcpInputSchema.safeParse({
181
+ productCode: "price_test_123",
182
+ quantity: 2,
183
+ });
184
+ expect(result.success).toBe(true);
185
+ });
186
+
187
+ it("accepts productCode alone (quantity optional)", () => {
188
+ const result = PurchaseMcpInputSchema.safeParse({
189
+ productCode: "price_test_123",
190
+ });
191
+ expect(result.success).toBe(true);
192
+ });
193
+
194
+ it("rejects a call carrying successUrl", () => {
195
+ const result = PurchaseMcpInputSchema.safeParse({
196
+ productCode: "price_test_123",
197
+ successUrl: "https://example.com/success",
198
+ });
199
+ expect(result.success).toBe(false);
200
+ });
201
+
202
+ it("rejects a call carrying cancelUrl", () => {
203
+ const result = PurchaseMcpInputSchema.safeParse({
204
+ productCode: "price_test_123",
205
+ cancelUrl: "https://example.com/cancel",
206
+ });
207
+ expect(result.success).toBe(false);
208
+ });
209
+
210
+ it("rejects a call carrying allowPromotionCodes", () => {
211
+ const result = PurchaseMcpInputSchema.safeParse({
212
+ productCode: "price_test_123",
213
+ allowPromotionCodes: true,
214
+ });
215
+ expect(result.success).toBe(false);
216
+ });
217
+
218
+ it("rejects a call carrying all three excluded fields at once", () => {
219
+ const result = PurchaseMcpInputSchema.safeParse({
220
+ productCode: "price_test_123",
221
+ successUrl: "https://attacker.example/phish",
222
+ cancelUrl: "https://attacker.example/cancel",
223
+ allowPromotionCodes: true,
224
+ });
225
+ expect(result.success).toBe(false);
226
+ });
227
+
228
+ it("still rejects missing/invalid productCode, same as the shared schema", () => {
229
+ expect(PurchaseMcpInputSchema.safeParse({}).success).toBe(false);
230
+ expect(
231
+ PurchaseMcpInputSchema.safeParse({ productCode: "" }).success,
232
+ ).toBe(false);
233
+ expect(
234
+ PurchaseMcpInputSchema.safeParse({ productCode: "has spaces" }).success,
235
+ ).toBe(false);
236
+ });
237
+
238
+ it("still rejects a quantity above the route's maximum of 999", () => {
239
+ const result = PurchaseMcpInputSchema.safeParse({
240
+ productCode: "price_x",
241
+ quantity: 1000,
242
+ });
243
+ expect(result.success).toBe(false);
244
+ });
98
245
  });
99
246
 
100
247
  // ── Output schema (live shape) ────────────────────────────────────────────────
@@ -121,79 +268,41 @@ describe("PurchaseOutputSchema", () => {
121
268
  // ── ailkPurchase handler ──────────────────────────────────────────────────────
122
269
 
123
270
  describe("ailkPurchase — valid input", () => {
124
- it("calls POST /v1/checkout/sessions with mapped payload", async () => {
125
- mockFetch.mockResolvedValueOnce({
126
- ok: true,
127
- status: 201,
128
- json: () =>
129
- Promise.resolve({
130
- sessionId: "cs_test_abc123",
131
- url: "https://checkout.stripe.com/pay/cs_test_abc123",
132
- }),
133
- });
271
+ it("calls POST /v1/checkout/sessions with the mapped payload", async () => {
272
+ createSession.mockResolvedValueOnce(
273
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_abc123"),
274
+ );
134
275
 
135
276
  await ailkPurchase({
136
- priceId: "price_test_123",
277
+ productCode: "price_test_123",
137
278
  quantity: 1,
138
- successUrl: "https://example.com/success",
139
- cancelUrl: "https://example.com/cancel",
140
- });
141
-
142
- expect(mockFetch).toHaveBeenCalledTimes(1);
143
- const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
144
- expect(url).toMatch(/\/v1\/checkout\/sessions$/);
145
- expect(opts.method).toBe("POST");
146
- const body = JSON.parse(opts.body as string) as Record<string, unknown>;
147
- expect(body.productCode).toBe("price_test_123");
148
- expect(body.quantity).toBe(1);
149
- });
150
-
151
- it("forwards allowPromotionCodes when supplied, omits it otherwise", async () => {
152
- const okResponse = () => ({
153
- ok: true,
154
- status: 201,
155
- json: () =>
156
- Promise.resolve({
157
- sessionId: "cs_test_promo",
158
- url: "https://checkout.stripe.com/pay/cs_test_promo",
159
- }),
160
279
  });
161
- mockFetch.mockResolvedValueOnce(okResponse());
162
280
 
163
- await ailkPurchase({
164
- priceId: "price_test_123",
165
- allowPromotionCodes: true,
166
- });
281
+ expect(createSession).toHaveBeenCalledTimes(1);
282
+ const payload = createSession.mock.calls[0]![0];
283
+ expect(payload.productCode).toBe("price_test_123");
284
+ expect(payload.quantity).toBe(1);
285
+ });
167
286
 
168
- const withFlag = JSON.parse(
169
- (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
170
- ) as Record<string, unknown>;
171
- expect(withFlag.allowPromotionCodes).toBe(true);
287
+ it("never forwards allowPromotionCodes — it is not part of the tool's input", async () => {
288
+ createSession.mockResolvedValueOnce(
289
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_promo"),
290
+ );
172
291
 
173
- mockFetch.mockResolvedValueOnce(okResponse());
174
- await ailkPurchase({ priceId: "price_test_123" });
292
+ await ailkPurchase({ productCode: "price_test_123" });
175
293
 
176
- const withoutFlag = JSON.parse(
177
- (mockFetch.mock.calls[1] as [string, RequestInit])[1].body as string,
178
- ) as Record<string, unknown>;
179
- expect(withoutFlag).not.toHaveProperty("allowPromotionCodes");
294
+ expect(createSession.mock.calls[0]![0]).not.toHaveProperty(
295
+ "allowPromotionCodes",
296
+ );
180
297
  });
181
298
 
182
299
  it("returns { url } on success", async () => {
183
- mockFetch.mockResolvedValueOnce({
184
- ok: true,
185
- status: 201,
186
- json: () =>
187
- Promise.resolve({
188
- sessionId: "cs_test_xyz",
189
- url: "https://checkout.stripe.com/pay/cs_test_xyz",
190
- }),
191
- });
300
+ createSession.mockResolvedValueOnce(
301
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_xyz"),
302
+ );
192
303
 
193
304
  const output = await ailkPurchase({
194
- priceId: "price_test_xyz",
195
- successUrl: "https://example.com/success",
196
- cancelUrl: "https://example.com/cancel",
305
+ productCode: "price_test_xyz",
197
306
  });
198
307
 
199
308
  expect(output).toEqual({
@@ -202,57 +311,125 @@ describe("ailkPurchase — valid input", () => {
202
311
  });
203
312
 
204
313
  it("output validates against PurchaseOutputSchema", async () => {
205
- mockFetch.mockResolvedValueOnce({
206
- ok: true,
207
- status: 201,
208
- json: () =>
209
- Promise.resolve({
210
- sessionId: "cs_test_valid",
211
- url: "https://checkout.stripe.com/pay/cs_test_valid",
212
- }),
213
- });
314
+ createSession.mockResolvedValueOnce(
315
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_valid"),
316
+ );
214
317
 
215
- const output = await ailkPurchase({ priceId: "price_test_valid" });
216
- const result = PurchaseOutputSchema.safeParse(output);
217
- expect(result.success).toBe(true);
318
+ const output = await ailkPurchase({ productCode: "price_test_valid" });
319
+ expect(PurchaseOutputSchema.safeParse(output).success).toBe(true);
218
320
  });
219
321
  });
220
322
 
221
- describe("ailkPurchase — invalid input (Zod errors)", () => {
222
- it("throws ZodError for missing priceId before any fetch", async () => {
223
- await expect(
224
- ailkPurchase({ quantity: 1 } as never),
225
- ).rejects.toThrow();
226
- expect(mockFetch).not.toHaveBeenCalled();
323
+ describe("ailkPurchase — invalid input (typed error body, no side effect)", () => {
324
+ it("returns validation_failed for a missing productCode before any call", async () => {
325
+ const result = await ailkPurchase({ quantity: 1 } as never);
326
+
327
+ expect(result).toMatchObject({ error: "validation_failed" });
328
+ expect(createSession).not.toHaveBeenCalled();
227
329
  });
228
330
 
229
- it("throws ZodError for empty priceId before any fetch", async () => {
230
- await expect(
231
- ailkPurchase({ priceId: "" }),
232
- ).rejects.toThrow();
233
- expect(mockFetch).not.toHaveBeenCalled();
331
+ it("returns validation_failed for an empty productCode before any call", async () => {
332
+ const result = await ailkPurchase({ productCode: "" });
333
+
334
+ expect(result).toMatchObject({ error: "validation_failed" });
335
+ expect(createSession).not.toHaveBeenCalled();
336
+ });
337
+
338
+ it("returns validation_failed for a call carrying successUrl (#6001 AC1)", async () => {
339
+ const result = await ailkPurchase({
340
+ productCode: "price_test_123",
341
+ successUrl: "https://attacker.example/phish",
342
+ } as never);
343
+
344
+ expect(result).toMatchObject({ error: "validation_failed" });
345
+ expect(createSession).not.toHaveBeenCalled();
346
+ });
347
+
348
+ it("returns validation_failed for a call carrying cancelUrl (#6001 AC1)", async () => {
349
+ const result = await ailkPurchase({
350
+ productCode: "price_test_123",
351
+ cancelUrl: "https://attacker.example/cancel",
352
+ } as never);
353
+
354
+ expect(result).toMatchObject({ error: "validation_failed" });
355
+ expect(createSession).not.toHaveBeenCalled();
356
+ });
357
+
358
+ it("returns validation_failed for a call carrying allowPromotionCodes (#6001 AC1)", async () => {
359
+ const result = await ailkPurchase({
360
+ productCode: "price_test_123",
361
+ allowPromotionCodes: true,
362
+ } as never);
363
+
364
+ expect(result).toMatchObject({ error: "validation_failed" });
365
+ expect(createSession).not.toHaveBeenCalled();
234
366
  });
235
367
  });
236
368
 
237
- describe("ailkPurchase — parity shape with purchaseAction", () => {
238
- it("success shape is { url: string } matching Server Action shape", async () => {
239
- mockFetch.mockResolvedValueOnce({
240
- ok: true,
241
- status: 201,
242
- json: () =>
243
- Promise.resolve({
244
- sessionId: "cs_test_parity",
245
- url: "https://checkout.stripe.com/pay/cs_test_parity",
246
- }),
369
+ describe("ailkPurchase — always derives redirects from AILK_BASE_URL (#6001 AC2)", () => {
370
+ it("derives both redirect URLs from AILK_BASE_URL when it is set", async () => {
371
+ process.env.AILK_BASE_URL = "https://example.com";
372
+ createSession.mockResolvedValueOnce(
373
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_base"),
374
+ );
375
+
376
+ await ailkPurchase({ productCode: "price_test_123" });
377
+
378
+ expect(createSession).toHaveBeenCalledWith({
379
+ productCode: "price_test_123",
380
+ successUrl:
381
+ "https://example.com/purchase/success?session_id={CHECKOUT_SESSION_ID}",
382
+ cancelUrl: "https://example.com/purchase/cancel",
247
383
  });
384
+ });
385
+
386
+ it("falls back to the tool's existing default when AILK_BASE_URL is unset", async () => {
387
+ delete process.env.AILK_BASE_URL;
388
+ delete process.env.NEXT_PUBLIC_BASE_URL;
389
+ createSession.mockResolvedValueOnce(
390
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_fallback"),
391
+ );
392
+
393
+ await ailkPurchase({ productCode: "price_test_123" });
394
+
395
+ expect(createSession).toHaveBeenCalledWith({
396
+ productCode: "price_test_123",
397
+ successUrl:
398
+ "http://localhost:3000/purchase/success?session_id={CHECKOUT_SESSION_ID}",
399
+ cancelUrl: "http://localhost:3000/purchase/cancel",
400
+ });
401
+ });
402
+
403
+ // #6084 AC-6/D1 — the literal template token survives, and cancelUrl
404
+ // carries no session parameter at all.
405
+ it("successUrl carries the literal {CHECKOUT_SESSION_ID} token; cancelUrl carries no session parameter (#6084 D1)", async () => {
406
+ process.env.AILK_BASE_URL = "https://example.com";
407
+ createSession.mockResolvedValueOnce(
408
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_braces"),
409
+ );
410
+
411
+ await ailkPurchase({ productCode: "price_test_123" });
412
+
413
+ const payload = createSession.mock.calls[0]![0] as {
414
+ successUrl: string;
415
+ cancelUrl: string;
416
+ };
417
+ expect(payload.successUrl).toContain("{CHECKOUT_SESSION_ID}");
418
+ expect(payload.cancelUrl).not.toContain("session_id");
419
+ expect(payload.cancelUrl).not.toContain("{CHECKOUT_SESSION_ID}");
420
+ });
421
+ });
422
+
423
+ describe("ailkPurchase — parity shape with purchaseAction", () => {
424
+ it("success shape is { url: string } matching the Server Action shape", async () => {
425
+ createSession.mockResolvedValueOnce(
426
+ sessionCreated("https://checkout.stripe.com/pay/cs_test_parity"),
427
+ );
248
428
 
249
429
  const output = await ailkPurchase({
250
- priceId: "price_parity",
251
- successUrl: "https://example.com/success",
252
- cancelUrl: "https://example.com/cancel",
430
+ productCode: "price_parity",
253
431
  });
254
432
 
255
- // Shape must match purchaseAction's success shape
256
433
  expect(output).toHaveProperty("url");
257
434
  expect(typeof (output as { url: string }).url).toBe("string");
258
435
  });
@@ -0,0 +1,69 @@
1
+ // Self-contained ESLint flat config for this scaffolded package.
2
+ //
3
+ // It deliberately does NOT depend on @working-theory/eslint-config — that
4
+ // package is internal to the AI Launch Kit monorepo and is not published, so
5
+ // it cannot be a dependency of a scaffolded starter. Everything below comes
6
+ // from public npm packages already pinned as devDependencies (see
7
+ // package.json): @eslint/js, globals and typescript-eslint.
8
+ //
9
+ // Source file name (#5556): this file is checked in as `_eslint.config.mjs`,
10
+ // not `eslint.config.mjs` — build-templates.mjs renames it at snapshot time
11
+ // (renameScaffoldLintConfigs()). A file literally named `eslint.config.mjs`
12
+ // anywhere under this monorepo (even one this repo's own root eslint.config.ts
13
+ // lists in its `ignores`) is auto-discovered by ESLint's flat-config nested
14
+ // resolution as a competing config root, which breaks the monorepo's own
15
+ // lint runs (verified live: typescript-eslint's "multiple candidate
16
+ // TSConfigRootDirs" parser error across unrelated packages/create-ailk
17
+ // files). The underscore prefix keeps it invisible to that discovery while
18
+ // it lives in this checkout; only the snapshot in templates/ — which ships
19
+ // to the customer, not this repo — gets the real filename.
20
+ import js from "@eslint/js";
21
+ import globals from "globals";
22
+ import tseslint from "typescript-eslint";
23
+
24
+ export default tseslint.config(
25
+ {
26
+ ignores: [
27
+ "**/.next/**",
28
+ "**/dist/**",
29
+ "**/generated/**",
30
+ "**/node_modules/**",
31
+ ],
32
+ },
33
+ js.configs.recommended,
34
+ ...tseslint.configs.recommended,
35
+ {
36
+ languageOptions: {
37
+ globals: {
38
+ ...globals.node,
39
+ ...globals.browser,
40
+ ...globals.jest,
41
+ },
42
+ // Explicit, self-contained root — this scaffold config is never meant
43
+ // to combine with any sibling scaffold package's config (each ships
44
+ // standalone), and leaving it unset makes typescript-eslint's
45
+ // candidate-root disambiguation ambiguous the moment more than one
46
+ // scaffold package's config resolves in the same ESLint process (e.g.
47
+ // this monorepo's own `templates/` build, where all four exist
48
+ // side by side; a real customer scaffold never has that problem,
49
+ // since it only ever ships and lints ONE of these four at a time).
50
+ parserOptions: {
51
+ tsconfigRootDir: import.meta.dirname,
52
+ },
53
+ },
54
+ rules: {
55
+ // Downgraded rather than off, so both stay visible without failing a
56
+ // fresh scaffold's lint:
57
+ // - unused-vars: most of the source follows the underscore-prefix
58
+ // convention for intentionally-unused bindings, which this rule's
59
+ // default options don't recognize; a handful of test fixtures also
60
+ // carry genuinely-unused locals this minimal config doesn't try to
61
+ // triage file-by-file.
62
+ // - require-imports: CommonJS require() is the correct form in Jest
63
+ // config/mock files and in a few test-only dynamic re-require
64
+ // patterns.
65
+ "@typescript-eslint/no-unused-vars": "warn",
66
+ "@typescript-eslint/no-require-imports": "warn",
67
+ },
68
+ },
69
+ );
@@ -22,6 +22,17 @@ module.exports = {
22
22
  '^@working-theory/aeo/(.*)$': '<rootDir>/../../packages/aeo/src/$1',
23
23
  '^@working-theory/content-adapters$': '<rootDir>/../../packages/content-adapters/src/index.ts',
24
24
  '^@working-theory/content-adapters/(.*)$': '<rootDir>/../../packages/content-adapters/src/$1',
25
+ // The constants + one-workspace predicate subpath only (#5876 spec D11) —
26
+ // no @prisma/client import, so ts-jest compiles it directly like every
27
+ // other workspace mapping below. The database/dist/ build is empty in a
28
+ // fresh checkout, and the published dist is ESM, which Jest's CJS preset
29
+ // can't load. The CLIENT root (`@working-theory/database`) stays
30
+ // deliberately UNMAPPED — see apps/mcp/src/lib/prisma.ts.
31
+ '^@working-theory/database/tenancy$': '<rootDir>/../../database/tenancy.ts',
32
+ // tenancy.ts's isSentinel import (above) is a VALUE import, so every
33
+ // suite that loads the tenancy subpath transitively loads this one too
34
+ // — mirrors apps/api/jest.config.cjs's identical mapping.
35
+ '^@working-theory/ids$': '<rootDir>/../../packages/ids/src/index.ts',
25
36
  '^@working-theory/license$': '<rootDir>/../../packages/license/src/index.ts',
26
37
  '^@working-theory/metadata$': '<rootDir>/../../packages/metadata/src/index.ts',
27
38
  '^@working-theory/schema$': '<rootDir>/../../packages/schema/src/index.ts',
@@ -61,6 +72,8 @@ module.exports = {
61
72
  '@working-theory/api-client': ['../../packages/api-client/src/index.ts'],
62
73
  '@working-theory/content-adapters': ['../../packages/content-adapters/src/index.ts'],
63
74
  '@working-theory/content-adapters/*': ['../../packages/content-adapters/src/*'],
75
+ '@working-theory/database/tenancy': ['../../database/tenancy.ts'],
76
+ '@working-theory/ids': ['../../packages/ids/src/index.ts'],
64
77
  '@working-theory/license': ['../../packages/license/src/index.ts'],
65
78
  '@working-theory/metadata': ['../../packages/metadata/src/index.ts'],
66
79
  '@working-theory/schema': ['../../packages/schema/src/index.ts'],
@@ -6,7 +6,7 @@
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "scripts": {
9
- "lint": "tsx ../../scripts/lint-package.ts",
9
+ "lint": "eslint .",
10
10
  "build": "tsc -b",
11
11
  "type-check": "tsc --noEmit",
12
12
  "dev": "node --experimental-vm-modules dist/index.js",
@@ -18,9 +18,11 @@
18
18
  "@fastify/cors": "^11.2.0",
19
19
  "@fastify/rate-limit": "^10.3.0",
20
20
  "@modelcontextprotocol/sdk": "^1.29.0",
21
+ "@prisma/adapter-pg": "^7.8.0",
21
22
  "@working-theory/aeo": "workspace:*",
22
23
  "@working-theory/api-client": "workspace:*",
23
24
  "@working-theory/content-adapters": "workspace:*",
25
+ "@working-theory/database": "workspace:*",
24
26
  "@working-theory/license": "workspace:*",
25
27
  "@working-theory/metadata": "workspace:*",
26
28
  "@working-theory/schema": "workspace:*",
@@ -34,6 +36,10 @@
34
36
  "@types/node": "^22.20.0",
35
37
  "jest": "^30.4.2",
36
38
  "ts-jest": "^29.4.11",
37
- "typescript": "^6.0.3"
39
+ "typescript": "^6.0.3",
40
+ "eslint": "^10.6.0",
41
+ "@eslint/js": "^10.0.1",
42
+ "globals": "^17.7.0",
43
+ "typescript-eslint": "^8.62.1"
38
44
  }
39
45
  }