create-ailk 0.21.0 → 0.23.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 (342) hide show
  1. package/component-catalog.json +186 -8
  2. package/component-catalog.md +9 -7
  3. package/dist/add-module.js +20 -14
  4. package/dist/add-route.d.ts +55 -1
  5. package/dist/add-route.js +294 -12
  6. package/dist/cli.js +27 -10
  7. package/dist/derive-architecture.d.ts +12 -3
  8. package/dist/derive-architecture.js +20 -8
  9. package/dist/design-package.js +7 -2
  10. package/dist/lib/design-checks.js +4 -2
  11. package/dist/lib/env-catalog.js +5 -5
  12. package/dist/lib/npm-auth.d.ts +9 -0
  13. package/dist/lib/npm-auth.js +48 -0
  14. package/dist/lib/scaffold-render-sweep.d.ts +3 -2
  15. package/dist/lib/scaffold-render-sweep.js +5 -8
  16. package/dist/module-architecture.js +99 -9
  17. package/dist/paid-sections.d.ts +21 -2
  18. package/dist/paid-sections.js +69 -7
  19. package/dist/programmatic.js +19 -5
  20. package/dist/seed-flows.d.ts +4 -1
  21. package/dist/seed-flows.js +50 -4
  22. package/dist/site-chrome.d.ts +8 -0
  23. package/dist/site-chrome.js +17 -1
  24. package/dist/spoke-checkout.d.ts +65 -41
  25. package/dist/spoke-checkout.js +208 -430
  26. package/dist/starter-grid.d.ts +0 -3
  27. package/dist/starter-grid.js +24 -14
  28. package/dist/surfaces.js +52 -26
  29. package/dist/sync-routes.d.ts +38 -1
  30. package/dist/sync-routes.js +187 -13
  31. package/dist/template-record.d.ts +4 -3
  32. package/dist/template-record.js +8 -4
  33. package/dist/topology.js +25 -1
  34. package/dist/update.js +15 -0
  35. package/package.json +3 -3
  36. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  37. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  38. package/site-templates/designs/minimalism/DESIGN.md +1 -1
  39. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  40. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  41. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  42. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  43. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  44. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  45. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  46. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  47. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  48. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  49. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  50. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  51. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  52. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  53. package/templates/.env.example +9 -7
  54. package/templates/.github/workflows/ci.yml +13 -0
  55. package/templates/README.md +1 -0
  56. package/templates/_gitignore +4 -0
  57. package/templates/apps/api/.env.example +27 -0
  58. package/templates/apps/api/CLAUDE.md +87 -17
  59. package/templates/apps/api/package.json +1 -0
  60. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  61. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  62. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  63. package/templates/apps/api/src/config/index.ts +109 -0
  64. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  65. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
  66. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
  67. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  68. package/templates/apps/api/src/lib/consent-store.ts +28 -0
  69. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  70. package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
  71. package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
  72. package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
  73. package/templates/apps/api/src/lib/stripe.ts +74 -4
  74. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  75. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  77. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  78. package/templates/apps/api/src/middleware/auth.ts +13 -6
  79. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  80. package/templates/apps/api/src/openapi/spec.ts +33 -3
  81. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
  82. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
  83. package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
  84. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  85. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
  86. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  87. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  88. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  89. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  90. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
  91. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  92. package/templates/apps/api/src/routes/consent/account.ts +268 -0
  93. package/templates/apps/api/src/routes/consent/index.ts +15 -7
  94. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
  95. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  96. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  97. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  98. package/templates/apps/api/src/routes/content/get-page.ts +4 -0
  99. package/templates/apps/api/src/routes/content/index.ts +16 -2
  100. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  101. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  102. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  103. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
  104. package/templates/apps/api/src/routes/leads/index.ts +164 -7
  105. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  106. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  111. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  112. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  113. package/templates/apps/api/src/routes/webhooks/stripe.ts +245 -36
  114. package/templates/apps/api/src/server.ts +163 -46
  115. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  116. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
  117. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  118. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1383 -133
  119. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
  120. package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
  121. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
  122. package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
  123. package/templates/apps/api/src/services/consent.ts +95 -0
  124. package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
  125. package/templates/apps/api/src/services/entitlements.ts +157 -1
  126. package/templates/apps/api/src/services/lead-routing.ts +431 -47
  127. package/templates/apps/api/src/services/magnet-completion.ts +242 -0
  128. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
  129. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  130. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
  131. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  132. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
  133. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
  134. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  135. package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
  136. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
  137. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  138. package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
  139. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  140. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  141. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  142. package/templates/apps/api/src/services/person.ts +164 -17
  143. package/templates/apps/api/src/services/plan-offerings.ts +125 -0
  144. package/templates/apps/api/src/services/public-site-resolver.ts +27 -15
  145. package/templates/apps/mcp/CLAUDE.md +22 -14
  146. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  147. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  148. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  149. package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
  150. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  151. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  152. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  153. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  154. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  155. package/templates/apps/mcp/src/server.ts +5 -0
  156. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  157. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  158. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  159. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  160. package/templates/apps/web/.env.example +24 -31
  161. package/templates/apps/web/CLAUDE.md +393 -9
  162. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
  163. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
  164. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
  165. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
  166. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
  167. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
  168. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
  169. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
  170. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
  171. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
  172. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
  173. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
  174. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
  175. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
  176. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
  177. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
  178. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
  179. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
  180. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
  181. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
  182. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
  183. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
  184. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
  185. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
  186. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
  187. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
  188. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
  189. package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
  190. package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
  191. package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
  192. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
  193. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
  194. package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
  195. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  196. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +295 -0
  197. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +120 -0
  198. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +23 -0
  199. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +319 -0
  200. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  201. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  202. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +141 -0
  203. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +424 -0
  204. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +72 -11
  205. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  206. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  207. package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
  208. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
  209. package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
  210. package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
  211. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  212. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
  213. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
  214. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +160 -13
  215. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +466 -36
  216. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
  217. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +172 -57
  218. package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
  219. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -60
  220. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
  221. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
  222. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +341 -4
  223. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  224. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  225. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  226. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
  227. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
  228. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  229. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  230. package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
  231. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
  232. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  233. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  234. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
  235. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  236. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
  237. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  238. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  239. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  240. package/templates/apps/web/app/[locale]/page.tsx +13 -1
  241. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  242. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  243. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  244. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  245. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  246. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  247. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
  248. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
  249. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
  250. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  251. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  252. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  253. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  254. package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
  255. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
  256. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
  257. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
  258. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  259. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  260. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  261. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  262. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  263. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
  264. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
  265. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
  266. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
  267. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  268. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  269. package/templates/apps/web/auth.ts +28 -2
  270. package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
  271. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  272. package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
  273. package/templates/apps/web/lib/__tests__/deck-page.test.ts +7 -1
  274. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  275. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
  276. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  277. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  278. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +101 -25
  279. package/templates/apps/web/lib/account-invite.ts +37 -0
  280. package/templates/apps/web/lib/auth-routes.ts +36 -2
  281. package/templates/apps/web/lib/checkout-config.ts +16 -0
  282. package/templates/apps/web/lib/consent-policies.ts +22 -0
  283. package/templates/apps/web/lib/deck-page.ts +7 -0
  284. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  285. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
  286. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
  287. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  288. package/templates/apps/web/lib/magnet-routes.ts +42 -0
  289. package/templates/apps/web/lib/modules.generated.ts +9 -0
  290. package/templates/apps/web/lib/newsletter.actions.ts +202 -0
  291. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  292. package/templates/apps/web/lib/posting-title.ts +17 -0
  293. package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
  294. package/templates/apps/web/lib/sign-up-agreements.ts +449 -0
  295. package/templates/apps/web/lib/site-chrome.ts +2 -0
  296. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  297. package/templates/apps/web/messages/en.json +71 -20
  298. package/templates/apps/web/middleware.ts +12 -2
  299. package/templates/apps/web/package.json +1 -0
  300. package/templates/apps/web/security-headers.config.ts +17 -0
  301. package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
  302. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
  303. package/templates/apps/web/src/lib/lead-write.ts +90 -0
  304. package/templates/apps/web/src/lib/rate-limit.ts +79 -0
  305. package/templates/content/en/BlogList/blog.mdx +4 -71
  306. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  307. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  308. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  309. package/templates/content/en/BookCall/start-project.mdx +1 -17
  310. package/templates/content/en/ContactPage/contact.mdx +6 -63
  311. package/templates/content/en/HomePage/home.mdx +3 -21
  312. package/templates/database/CHANGELOG.md +69 -0
  313. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
  314. package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
  315. package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
  316. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  317. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  318. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  319. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  320. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  321. package/templates/database/__tests__/seed.test.ts +64 -1
  322. package/templates/database/auth/schema.prisma +4 -0
  323. package/templates/database/config/schema.prisma +91 -0
  324. package/templates/database/inbox/schema.prisma +17 -2
  325. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
  326. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
  327. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  328. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  329. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  330. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  331. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  332. package/templates/database/package.json +1 -1
  333. package/templates/database/seed.ts +62 -3
  334. package/versions.json +35 -35
  335. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  336. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
  337. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  338. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  339. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  340. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  341. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  342. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -14,13 +14,35 @@
14
14
  * offering fields on its own frontmatter answer instead — a listing that
15
15
  * declares `price` + `priceCurrency` yields one tier named for the page. A
16
16
  * listing with neither an authored offer nor a price still returns `null`: no
17
- * price means no tier, never a fabricated zero.
17
+ * price means no tier, never a fabricated zero. `getPricingTiers`
18
+ * (`@working-theory/content-adapters`) documents the mapping.
19
+ *
20
+ * HTTP twin (#7178): `GET /v1/content/pricing` in `apps/api` takes the same
21
+ * arguments and answers through the same `getPricingTiers` — the shared seam
22
+ * the pair proves parity at. The tool still reads content locally, so it
23
+ * needs no API to answer.
24
+ *
25
+ * Offerings (#8933, spec D8): when the site sells through its connected
26
+ * Stripe catalog and `slug` is omitted or `pricing`, the tool answers the
27
+ * workspace's Offerings shown set instead — read over HTTP by
28
+ * `lib/shown-offerings.ts` (D6: only on a `claimed` host, at most
29
+ * `MCP_SHOWN_SET_BUDGET_MS`) and mapped by `pricingTiersFromShownSet`, the
30
+ * function the twin route answers through too, so each tier also carries
31
+ * `interval` and the full `lookupKey`. Names, descriptions and prices are
32
+ * seller-provided data returned as plain JSON values (T5). Any other slug, an
33
+ * unclaimed host, a workspace with no current set, a failed read or a mapping
34
+ * that throws answers the page content below, unchanged.
18
35
  *
19
36
  * Input: { slug?: string, locale?: string }
20
- * Output: { tiers: Array<{ name, price, priceCurrency, description? }> } | null
37
+ * Output: { tiers: Array<{ name, price, priceCurrency, description?, interval?, lookupKey? }> } | null
21
38
  * Session/scope: none — read-only public content.
22
39
  */
23
40
 
41
+ import {
42
+ getPricingTiers,
43
+ pricingTiersFromShownSet,
44
+ shownSetAnswersPricing,
45
+ } from "@working-theory/content-adapters";
24
46
  import {
25
47
  getPricingInputSchema,
26
48
  getPricingOutputSchema,
@@ -33,12 +55,14 @@ import {
33
55
  mcpContentRoot,
34
56
  } from "../lib/content-adapter.js";
35
57
  import { resolveLocale } from "../lib/resolve-locale.js";
58
+ import {
59
+ mcpCurrentShownSet,
60
+ reportMappingFailure,
61
+ } from "../lib/shown-offerings.js";
36
62
 
37
63
  export { getPricingInputSchema, getPricingOutputSchema };
38
64
  export type { GetPricingInput, GetPricingOutput };
39
65
 
40
- const DEFAULT_SLUG = "pricing";
41
-
42
66
  /**
43
67
  * get_pricing handler.
44
68
  *
@@ -51,44 +75,20 @@ export async function ailkGetPricing(
51
75
  ): Promise<GetPricingOutput> {
52
76
  getPricingInputSchema.parse(input);
53
77
 
78
+ if (shownSetAnswersPricing(input.slug)) {
79
+ const shownSet = await mcpCurrentShownSet();
80
+ if (shownSet !== null) {
81
+ try {
82
+ return pricingTiersFromShownSet(shownSet);
83
+ } catch (err) {
84
+ reportMappingFailure(err);
85
+ }
86
+ }
87
+ }
88
+
54
89
  const root = contentRoot ?? mcpContentRoot();
55
90
  const locale = resolveLocale(input.locale);
56
91
  const adapter = getMcpContentAdapter({ contentRoot: root, locale });
57
92
 
58
- const slug = input.slug ?? DEFAULT_SLUG;
59
- const data = await adapter.extractStructuredData("Product", slug, locale);
60
-
61
- const schema = (data ?? {}) as Record<string, unknown>;
62
-
63
- // Support both a top-level `offers` array and a single offer object.
64
- const offersRaw = schema["offers"];
65
- const offersArr: Record<string, unknown>[] = Array.isArray(offersRaw)
66
- ? (offersRaw as Record<string, unknown>[])
67
- : offersRaw && typeof offersRaw === "object"
68
- ? [offersRaw as Record<string, unknown>]
69
- : [];
70
-
71
- if (offersArr.length === 0) {
72
- const page = await adapter.getPageBySlug("Product", slug, locale);
73
- if (!page || page.price === undefined) return null;
74
- return getPricingOutputSchema.parse({
75
- tiers: [
76
- {
77
- name: page.title,
78
- price: page.price,
79
- priceCurrency: page.priceCurrency ?? "USD",
80
- ...(page.summary !== undefined && { description: page.summary }),
81
- },
82
- ],
83
- });
84
- }
85
-
86
- const tiers = offersArr.map((offer) => ({
87
- name: (offer["name"] as string) ?? "",
88
- price: (offer["price"] as string | number) ?? "",
89
- priceCurrency: (offer["priceCurrency"] as string) ?? "USD",
90
- description: offer["description"] as string | undefined,
91
- }));
92
-
93
- return getPricingOutputSchema.parse({ tiers });
93
+ return getPricingTiers(adapter, { slug: input.slug, locale });
94
94
  }
@@ -57,11 +57,24 @@
57
57
  * through `@working-theory/api-client` per the architecture invariants (apps
58
58
  * may not import each other; the shared HTTP surface lives in packages/).
59
59
  *
60
- * Spec: `docs/specs/5971-start-purchase-hosted-checkout-spec.md`.
60
+ * THE KEY — the full Offerings lookup_key (#8935). `productCode` is a price's
61
+ * full lookup_key (contract §3.7), the `lookupKey` `get_pricing` and
62
+ * `get_offerings` return. After the claim and the strict schema, the one
63
+ * §3.7 parser, `parseLookupKey`, refuses an ungrammatical key before any HTTP
64
+ * call, with the same not-found body every route 404 maps to (A3, A5). The
65
+ * workspace stays the server's (A1): the api-client attaches the
66
+ * deployment's own spoke key, and the route alone decides whether the key is
67
+ * shown and org-enabled. A spoke whose Organization has no Stripe connection
68
+ * gets the route's same 404, never a session on the hub's own env key, so
69
+ * this tool refuses it as the web buy route does (#9126).
70
+ *
71
+ * Spec: `docs/specs/5971-start-purchase-hosted-checkout-spec.md`,
72
+ * `docs/specs/8935-start-purchase-sells-a-named-spec.md`.
61
73
  */
62
74
 
63
75
  import { checkout } from "@working-theory/api-client";
64
76
  import {
77
+ parseLookupKey,
65
78
  purchaseMcpInputSchema,
66
79
  type PurchaseMcpInput,
67
80
  type PurchaseOutput,
@@ -83,6 +96,15 @@ function commerceDisabledBody(): ErrorBody {
83
96
  return notFoundError("Not found");
84
97
  }
85
98
 
99
+ /**
100
+ * The one not-found body for an offering (#8935 D2, A5): an ungrammatical key
101
+ * refused before the call and every route 404 answer this same value, and
102
+ * neither echoes the caller's key.
103
+ */
104
+ function offeringNotFoundBody(): ErrorBody {
105
+ return notFoundError("No offering matches that product code.");
106
+ }
107
+
86
108
  /**
87
109
  * Export name `ailkPurchase` satisfies the parity-test naming convention
88
110
  * (ailk<PascalVerb>) so the harness can discover it.
@@ -127,6 +149,12 @@ export async function ailkPurchase(
127
149
 
128
150
  const { productCode, quantity } = parsed.data;
129
151
 
152
+ // #8935 D2 — a key outside the §3.7 grammar can never sell, so it is
153
+ // refused here, before the route spends a rate-limit token on it.
154
+ if (parseLookupKey(productCode) === null) {
155
+ return offeringNotFoundBody();
156
+ }
157
+
130
158
  // successUrl/cancelUrl are ALWAYS derived from baseUrl (#6001) — never
131
159
  // accepted from the caller. allowPromotionCodes is never forwarded: it is
132
160
  // not part of purchaseMcpInputSchema, so it can never be present here.
@@ -152,7 +180,7 @@ export async function ailkPurchase(
152
180
  // states and tests that invariant ("Error responses never echo user
153
181
  // input", apps/api/src/routes/checkout/sessions.ts), and parity means
154
182
  // the pair answers alike.
155
- return notFoundError("No offering matches that product code.");
183
+ return offeringNotFoundBody();
156
184
  }
157
185
  return validationError(
158
186
  `checkout-unavailable: POST /v1/checkout/sessions returned ${result.status}`,
@@ -83,6 +83,21 @@ const WORKSPACE_TOOL_ENTRIES: Record<
83
83
  specifier: "./tools/inbox_submissions.js",
84
84
  exportName: "ailkMarkInboxSubmissionDone",
85
85
  },
86
+ list_listing_applications: {
87
+ specifier: "./tools/inbox_applications.js",
88
+ exportName: "ailkListListingApplications",
89
+ },
90
+ get_listing_application: {
91
+ specifier: "./tools/inbox_applications.js",
92
+ exportName: "ailkGetListingApplication",
93
+ },
94
+ decide_listing_application: {
95
+ specifier: "./tools/inbox_applications.js",
96
+ exportName: "ailkDecideListingApplication",
97
+ },
98
+ // #9137 — the pre-rename names, deprecated aliases of the three above for
99
+ // one release: each export is the same function, so the same arguments and
100
+ // result.
86
101
  list_inbox_applications: {
87
102
  specifier: "./tools/inbox_applications.js",
88
103
  exportName: "ailkListInboxApplications",
@@ -92,6 +92,20 @@ CONTENT_ADAPTER=mdx
92
92
  # origin the browser never sees.
93
93
  # NEXT_PUBLIC_AILK_API_URL=http://localhost:3002
94
94
 
95
+ # =============================================================================
96
+ # The /contact lead's site marker (server-side only) — #8747
97
+ # =============================================================================
98
+ # This site's slug. The /contact Server Action writes it into every lead as
99
+ # payload.site (with payload.page = "contact"), so a hub serving several sites
100
+ # — or one site routing leads to two workspaces — can tell where a lead came
101
+ # from. Use the site's short name, lowercase letters, digits and hyphens (for
102
+ # example "wendihenry"); apps/mcp's capture_lead reads the same variable. Unset
103
+ # or blank leaves the lead with no site marker — nothing is invented, and a
104
+ # visitor-sent "site" field is dropped. The marker is attribution only: the
105
+ # lead route is public, so never gate access on it. The action sends no
106
+ # siteId; the API names the workspace itself (by AILK_SPOKE_KEY on a hub).
107
+ # AILK_LEAD_SITE=
108
+
95
109
  # =============================================================================
96
110
  # Listicle directory (#7521) — switched on by the site's listing key (#7872)
97
111
  # =============================================================================
@@ -247,37 +261,16 @@ CONTENT_ADAPTER=mdx
247
261
  # BETTER_STACK_HEARTBEAT_URL=
248
262
 
249
263
  # =============================================================================
250
- # Scheduling — the scheduling module's booking page (#4633)
264
+ # Scheduling — the scheduling module's booking pages (#4633, #8843)
251
265
  # =============================================================================
252
- # Which event `/meet` books, as "<owner>/<event-slug>" — the path of the
253
- # provider's own public booking page. Read server-side in
254
- # app/[locale]/meet/page.tsx and handed to the `booker` section, which
255
- # sends it back to apps/api's /v1/schedule/* routes to name the event.
256
- #
257
- # Unset is a supported state: `/meet` 404s, exactly as if the scheduling
258
- # module were not installed.
259
- #
260
- # NOT a credential, and deliberately NOT a NEXT_PUBLIC_ variable — the API key
261
- # it pairs with (CAL_API_KEY) lives in apps/api and this app never reads it, so
262
- # keeping the whole scheduling config surface server-read is what makes that
263
- # boundary checkable rather than merely intended.
264
- # SCHEDULING_CAL_LINK=owner/event-slug
265
- #
266
- # The booker's /v1/schedule/* calls go through this site's same-origin relay
267
- # (see the relay block above, #7644), so the baseline CSP's
268
- # `connect-src 'self'` already covers them — no CSP extension is needed for
269
- # them, whatever NEXT_PUBLIC_AILK_API_URL is set to.
266
+ # No variable here. /meet, /private and /meet/<slug> read every meeting fact
267
+ # — which meetings are Live, public or private, their titles, lengths and
268
+ # Cal.com links — from apps/api's GET /v1/schedule/meetings, which resolves
269
+ # the workspace from this site (the site alias keyless, a spoke by its key)
270
+ # and composes each Cal.com link from that workspace's own account. The
271
+ # one-meeting "<owner>/<event-slug>" variable this block used to set is
272
+ # retired, and nothing reads it.
270
273
  #
271
274
  # On a SPOKE (create-ailk --topology spoke, #7872) this site has no apps/api:
272
- # /meet lists the meeting types the hub stores for this spoke's workspace and
273
- # books through the hub at AILK_API_URL with AILK_SPOKE_KEY. Then:
274
- # - the FIRST segment (the owner) must name the provider account the hub
275
- # uses for this spoke's workspace (its scheduling connection). It composes
276
- # each booker's calLink; it is not an access control — the hub checks a
277
- # spoke booking's slug, never its owner.
278
- # - the SECOND segment is ignored: a workspace with no stored meeting-type
279
- # list shows no meetings, because the hub books only a slug it stores.
280
- # - the booker's two calls count against the relay's per-visitor budget,
281
- # which holds per visitor only behind an edge that overwrites
282
- # x-forwarded-for and x-real-ip (Vercel does); elsewhere every visitor
283
- # shares one budget.
275
+ # the pages read the hub's meetings for this spoke's workspace at AILK_API_URL
276
+ # with AILK_SPOKE_KEY, so a workspace with no stored meeting list shows none.