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,202 +0,0 @@
1
- // oss: false
2
- /**
3
- * Tests for the create_page MCP tool handler.
4
- *
5
- * D14a Stage 1 Unit C — Layer 1 / Layer 2 acceptance criteria mapped here:
6
- * - Free tier → canonical payment_required body, no adapter call.
7
- * - Bad frontmatter → validation_failed, no adapter call.
8
- * - Slug-already-exists → conflict body with details.existingVersion echoed
9
- * (so the agent can pivot to update without re-fetch).
10
- * - Pro tier + valid input + new slug → { page, version } success body.
11
- *
12
- * Adapter is injected as the second argument so this unit test does not need
13
- * Stage 1 Unit A's MDX implementation in place. The default adapter resolution
14
- * (createMdxAdapter against process.cwd()/content) is exercised by integration
15
- * tests once Unit A lands.
16
- */
17
-
18
- import { AdapterWriteError, type ContentAdapter } from "@working-theory/content-adapters";
19
-
20
- const mockCheckLicense = jest.fn();
21
-
22
- jest.mock("@working-theory/license", () => ({
23
- checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
24
- }));
25
-
26
- import { ailkCreatePage } from "../src/tools/create_page.js";
27
-
28
- interface AdapterCalls {
29
- createPage: jest.Mock;
30
- getPageBySlug: jest.Mock;
31
- getPageVersion: jest.Mock;
32
- }
33
-
34
- function makeAdapter(overrides: Partial<AdapterCalls> = {}): ContentAdapter {
35
- const calls: AdapterCalls = {
36
- createPage: jest.fn(),
37
- getPageBySlug: jest.fn().mockResolvedValue(null),
38
- getPageVersion: jest.fn(),
39
- ...overrides,
40
- };
41
- // eslint-disable-next-line no-restricted-syntax -- #506/#507: pre-existing test-hygiene violation
42
- return {
43
- getPageBySlug: calls.getPageBySlug,
44
- listPages: jest.fn().mockResolvedValue([]),
45
- getDetailBySlug: jest.fn().mockResolvedValue(null),
46
- listDetail: jest.fn().mockResolvedValue([]),
47
- searchPages: jest.fn().mockResolvedValue([]),
48
- extractStructuredData: jest.fn().mockResolvedValue(null),
49
- createPage: calls.createPage,
50
- updatePage: jest.fn(),
51
- deletePage: jest.fn(),
52
- getPageVersion: calls.getPageVersion,
53
- } as unknown as ContentAdapter;
54
- }
55
-
56
- const VALID_INPUT = {
57
- type: "Product" as const,
58
- slug: "widget-pro",
59
- page: {
60
- title: "Widget Pro",
61
- description: "The professional widget.",
62
- },
63
- body: "# Widget Pro\n\nProfessional-grade widget.",
64
- };
65
-
66
- describe("ailkCreatePage", () => {
67
- beforeEach(() => {
68
- mockCheckLicense.mockReset();
69
- });
70
-
71
- describe("license gating", () => {
72
- it("returns canonical payment_required body for free tier; adapter not called", async () => {
73
- mockCheckLicense.mockResolvedValue({
74
- tier: "free",
75
- plan: null,
76
- features: [],
77
- expiresAt: null,
78
- revoked: false,
79
- });
80
- const adapter = makeAdapter();
81
-
82
- const result = await ailkCreatePage(VALID_INPUT, adapter);
83
-
84
- expect(result).toMatchObject({ error: "payment_required" });
85
- expect(adapter.createPage).not.toHaveBeenCalled();
86
- });
87
- });
88
-
89
- describe("validation", () => {
90
- beforeEach(() => {
91
- mockCheckLicense.mockResolvedValue({
92
- tier: "pro",
93
- plan: "pro",
94
- features: [],
95
- expiresAt: null,
96
- revoked: false,
97
- });
98
- });
99
-
100
- it("returns validation_failed when frontmatter is missing required fields", async () => {
101
- const adapter = makeAdapter();
102
- const result = await ailkCreatePage(
103
- {
104
- ...VALID_INPUT,
105
- page: { title: "Only title" }, // missing description
106
- },
107
- adapter,
108
- );
109
- expect(result).toMatchObject({ error: "validation_failed" });
110
- expect(adapter.createPage).not.toHaveBeenCalled();
111
- });
112
-
113
- it("returns validation_failed when body is empty", async () => {
114
- const adapter = makeAdapter();
115
- const result = await ailkCreatePage(
116
- { ...VALID_INPUT, body: "" },
117
- adapter,
118
- );
119
- expect(result).toMatchObject({ error: "validation_failed" });
120
- expect(adapter.createPage).not.toHaveBeenCalled();
121
- });
122
-
123
- it("returns validation_failed for unknown SchemaOrgType", async () => {
124
- const adapter = makeAdapter();
125
- const result = await ailkCreatePage(
126
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
- { ...VALID_INPUT, type: "NotAType" as any },
128
- adapter,
129
- );
130
- expect(result).toMatchObject({ error: "validation_failed" });
131
- expect(adapter.createPage).not.toHaveBeenCalled();
132
- });
133
- });
134
-
135
- describe("slug conflict", () => {
136
- it("returns conflict with details.existingVersion when slug already exists", async () => {
137
- mockCheckLicense.mockResolvedValue({
138
- tier: "pro",
139
- plan: "pro",
140
- features: [],
141
- expiresAt: null,
142
- revoked: false,
143
- });
144
- const adapter = makeAdapter({
145
- createPage: jest
146
- .fn()
147
- .mockRejectedValue(
148
- new AdapterWriteError(
149
- "conflict",
150
- "Page already exists at Product/widget-pro.mdx",
151
- { version: "abc123" },
152
- ),
153
- ),
154
- });
155
-
156
- const result = await ailkCreatePage(VALID_INPUT, adapter);
157
-
158
- expect(result).toMatchObject({
159
- error: "conflict",
160
- details: { version: "abc123" },
161
- });
162
- });
163
- });
164
-
165
- describe("happy path", () => {
166
- it("returns { page, version } when adapter succeeds", async () => {
167
- mockCheckLicense.mockResolvedValue({
168
- tier: "pro",
169
- plan: "pro",
170
- features: [],
171
- expiresAt: null,
172
- revoked: false,
173
- });
174
- const persistedPage = {
175
- type: "Product" as const,
176
- slug: "widget-pro",
177
- title: "Widget Pro",
178
- description: "The professional widget.",
179
- };
180
- const adapter = makeAdapter({
181
- createPage: jest
182
- .fn()
183
- .mockResolvedValue({ page: persistedPage, version: "v1" }),
184
- });
185
-
186
- const result = await ailkCreatePage(VALID_INPUT, adapter);
187
-
188
- expect(result).toEqual({ page: persistedPage, version: "v1" });
189
- expect(adapter.createPage).toHaveBeenCalledTimes(1);
190
- const callArgs = (adapter.createPage as jest.Mock).mock.calls[0];
191
- expect(callArgs[0]).toBe("Product");
192
- expect(callArgs[1]).toBe("widget-pro");
193
- expect(callArgs[2]).toMatchObject({
194
- type: "Product",
195
- slug: "widget-pro",
196
- title: "Widget Pro",
197
- description: "The professional widget.",
198
- body: "# Widget Pro\n\nProfessional-grade widget.",
199
- });
200
- });
201
- });
202
- });
@@ -1,177 +0,0 @@
1
- // oss: false
2
- /**
3
- * Tests for the delete_page MCP tool handler.
4
- *
5
- * D14a Stage 1 Unit C — Layer 1 / Layer 2 acceptance criteria mapped here:
6
- * - Free tier → payment_required, no adapter call.
7
- * - Missing ifMatch → validation_failed.
8
- * - Stale ifMatch → conflict with details.currentVersion echoed.
9
- * - Slug not found → not_found body.
10
- * - Pro + matching ifMatch → { ok: true } success body.
11
- */
12
-
13
- import { AdapterWriteError, type ContentAdapter } from "@working-theory/content-adapters";
14
-
15
- const mockCheckLicense = jest.fn();
16
-
17
- jest.mock("@working-theory/license", () => ({
18
- checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
19
- }));
20
-
21
- import { ailkDeletePage } from "../src/tools/delete_page.js";
22
-
23
- function makeAdapter(
24
- overrides: Partial<{ deletePage: jest.Mock }> = {},
25
- ): ContentAdapter {
26
- // eslint-disable-next-line no-restricted-syntax -- #506/#507: pre-existing test-hygiene violation
27
- return {
28
- getPageBySlug: jest.fn().mockResolvedValue(null),
29
- listPages: jest.fn().mockResolvedValue([]),
30
- getDetailBySlug: jest.fn().mockResolvedValue(null),
31
- listDetail: jest.fn().mockResolvedValue([]),
32
- searchPages: jest.fn().mockResolvedValue([]),
33
- extractStructuredData: jest.fn().mockResolvedValue(null),
34
- createPage: jest.fn(),
35
- updatePage: jest.fn(),
36
- deletePage: overrides.deletePage ?? jest.fn().mockResolvedValue(undefined),
37
- getPageVersion: jest.fn(),
38
- } as unknown as ContentAdapter;
39
- }
40
-
41
- const VALID_INPUT = {
42
- type: "Product" as const,
43
- slug: "widget-pro",
44
- ifMatch: "v1",
45
- };
46
-
47
- describe("ailkDeletePage", () => {
48
- beforeEach(() => {
49
- mockCheckLicense.mockReset();
50
- });
51
-
52
- it("returns payment_required for free tier; adapter not called", async () => {
53
- mockCheckLicense.mockResolvedValue({
54
- tier: "free",
55
- plan: null,
56
- features: [],
57
- expiresAt: null,
58
- revoked: false,
59
- });
60
- const adapter = makeAdapter();
61
-
62
- const result = await ailkDeletePage(VALID_INPUT, adapter);
63
-
64
- expect(result).toMatchObject({ error: "payment_required" });
65
- expect(adapter.deletePage).not.toHaveBeenCalled();
66
- });
67
-
68
- it("returns validation_failed when slug violates SLUG_PATTERN (issue #398 — defence-in-depth parity with create/update)", async () => {
69
- mockCheckLicense.mockResolvedValue({
70
- tier: "pro",
71
- plan: "pro",
72
- features: [],
73
- expiresAt: null,
74
- revoked: false,
75
- });
76
- const adapter = makeAdapter();
77
-
78
- const result = await ailkDeletePage(
79
- { ...VALID_INPUT, slug: "../etc/passwd" },
80
- adapter,
81
- );
82
-
83
- expect(result).toMatchObject({ error: "validation_failed" });
84
- expect(adapter.deletePage).not.toHaveBeenCalled();
85
- });
86
-
87
- it("returns validation_failed when ifMatch is missing", async () => {
88
- mockCheckLicense.mockResolvedValue({
89
- tier: "pro",
90
- plan: "pro",
91
- features: [],
92
- expiresAt: null,
93
- revoked: false,
94
- });
95
- const adapter = makeAdapter();
96
- const { ifMatch: _ignored, ...inputNoIfMatch } = VALID_INPUT;
97
-
98
- const result = await ailkDeletePage(
99
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
- inputNoIfMatch as any,
101
- adapter,
102
- );
103
-
104
- expect(result).toMatchObject({ error: "validation_failed" });
105
- expect(adapter.deletePage).not.toHaveBeenCalled();
106
- });
107
-
108
- it("returns conflict on stale ifMatch", async () => {
109
- mockCheckLicense.mockResolvedValue({
110
- tier: "pro",
111
- plan: "pro",
112
- features: [],
113
- expiresAt: null,
114
- revoked: false,
115
- });
116
- const adapter = makeAdapter({
117
- deletePage: jest
118
- .fn()
119
- .mockRejectedValue(
120
- new AdapterWriteError(
121
- "conflict",
122
- "Version mismatch on Product/widget-pro.mdx",
123
- { currentVersion: "v9" },
124
- ),
125
- ),
126
- });
127
-
128
- const result = await ailkDeletePage(VALID_INPUT, adapter);
129
-
130
- expect(result).toMatchObject({
131
- error: "conflict",
132
- details: { currentVersion: "v9" },
133
- });
134
- });
135
-
136
- it("returns not_found when adapter signals missing slug", async () => {
137
- mockCheckLicense.mockResolvedValue({
138
- tier: "pro",
139
- plan: "pro",
140
- features: [],
141
- expiresAt: null,
142
- revoked: false,
143
- });
144
- const adapter = makeAdapter({
145
- deletePage: jest
146
- .fn()
147
- .mockRejectedValue(
148
- new AdapterWriteError(
149
- "not_found",
150
- "No page at Product/widget-pro.mdx",
151
- ),
152
- ),
153
- });
154
-
155
- const result = await ailkDeletePage(VALID_INPUT, adapter);
156
-
157
- expect(result).toMatchObject({ error: "not_found" });
158
- });
159
-
160
- it("returns { ok: true } on happy path", async () => {
161
- mockCheckLicense.mockResolvedValue({
162
- tier: "pro",
163
- plan: "pro",
164
- features: [],
165
- expiresAt: null,
166
- revoked: false,
167
- });
168
- const adapter = makeAdapter();
169
-
170
- const result = await ailkDeletePage(VALID_INPUT, adapter);
171
-
172
- expect(result).toEqual({ ok: true });
173
- expect(adapter.deletePage).toHaveBeenCalledWith("Product", "widget-pro", {
174
- ifMatch: "v1",
175
- });
176
- });
177
- });
@@ -1,196 +0,0 @@
1
- // oss: false
2
- /**
3
- * Tests for the update_page MCP tool handler.
4
- *
5
- * D14a Stage 1 Unit C — Layer 1 / Layer 2 acceptance criteria mapped here:
6
- * - Free tier → payment_required, no adapter call.
7
- * - Missing ifMatch → validation_failed (caught by input schema).
8
- * - Stale ifMatch → conflict body with details.currentVersion echoed.
9
- * - Slug not found → not_found body.
10
- * - Bad frontmatter → validation_failed, no adapter call.
11
- * - Pro + valid + matching ifMatch → { page, version } success body.
12
- */
13
-
14
- import { AdapterWriteError, type ContentAdapter } from "@working-theory/content-adapters";
15
-
16
- const mockCheckLicense = jest.fn();
17
-
18
- jest.mock("@working-theory/license", () => ({
19
- checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
20
- }));
21
-
22
- import { ailkUpdatePage } from "../src/tools/update_page.js";
23
-
24
- function makeAdapter(
25
- overrides: Partial<{
26
- updatePage: jest.Mock;
27
- }> = {},
28
- ): ContentAdapter {
29
- // eslint-disable-next-line no-restricted-syntax -- #506/#507: pre-existing test-hygiene violation
30
- return {
31
- getPageBySlug: jest.fn().mockResolvedValue(null),
32
- listPages: jest.fn().mockResolvedValue([]),
33
- getDetailBySlug: jest.fn().mockResolvedValue(null),
34
- listDetail: jest.fn().mockResolvedValue([]),
35
- searchPages: jest.fn().mockResolvedValue([]),
36
- extractStructuredData: jest.fn().mockResolvedValue(null),
37
- createPage: jest.fn(),
38
- updatePage: overrides.updatePage ?? jest.fn(),
39
- deletePage: jest.fn(),
40
- getPageVersion: jest.fn(),
41
- } as unknown as ContentAdapter;
42
- }
43
-
44
- const VALID_INPUT = {
45
- type: "Product" as const,
46
- slug: "widget-pro",
47
- page: {
48
- title: "Widget Pro v2",
49
- description: "Updated.",
50
- },
51
- body: "# Widget Pro v2\n",
52
- ifMatch: "v1",
53
- };
54
-
55
- describe("ailkUpdatePage", () => {
56
- beforeEach(() => {
57
- mockCheckLicense.mockReset();
58
- });
59
-
60
- it("returns payment_required for free tier; adapter not called", async () => {
61
- mockCheckLicense.mockResolvedValue({
62
- tier: "free",
63
- plan: null,
64
- features: [],
65
- expiresAt: null,
66
- revoked: false,
67
- });
68
- const adapter = makeAdapter();
69
-
70
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
71
-
72
- expect(result).toMatchObject({ error: "payment_required" });
73
- expect(adapter.updatePage).not.toHaveBeenCalled();
74
- });
75
-
76
- it("returns validation_failed when ifMatch is missing", async () => {
77
- mockCheckLicense.mockResolvedValue({
78
- tier: "pro",
79
- plan: "pro",
80
- features: [],
81
- expiresAt: null,
82
- revoked: false,
83
- });
84
- const adapter = makeAdapter();
85
- const { ifMatch: _ignored, ...inputNoIfMatch } = VALID_INPUT;
86
-
87
- const result = await ailkUpdatePage(
88
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
- inputNoIfMatch as any,
90
- adapter,
91
- );
92
-
93
- expect(result).toMatchObject({ error: "validation_failed" });
94
- expect(adapter.updatePage).not.toHaveBeenCalled();
95
- });
96
-
97
- it("returns conflict with details.currentVersion on stale ifMatch", async () => {
98
- mockCheckLicense.mockResolvedValue({
99
- tier: "pro",
100
- plan: "pro",
101
- features: [],
102
- expiresAt: null,
103
- revoked: false,
104
- });
105
- const adapter = makeAdapter({
106
- updatePage: jest
107
- .fn()
108
- .mockRejectedValue(
109
- new AdapterWriteError(
110
- "conflict",
111
- "Version mismatch on Product/widget-pro.mdx",
112
- { currentVersion: "v9" },
113
- ),
114
- ),
115
- });
116
-
117
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
118
-
119
- expect(result).toMatchObject({
120
- error: "conflict",
121
- details: { currentVersion: "v9" },
122
- });
123
- });
124
-
125
- it("returns not_found when adapter signals missing slug", async () => {
126
- mockCheckLicense.mockResolvedValue({
127
- tier: "pro",
128
- plan: "pro",
129
- features: [],
130
- expiresAt: null,
131
- revoked: false,
132
- });
133
- const adapter = makeAdapter({
134
- updatePage: jest
135
- .fn()
136
- .mockRejectedValue(
137
- new AdapterWriteError(
138
- "not_found",
139
- "No page at Product/widget-pro.mdx",
140
- ),
141
- ),
142
- });
143
-
144
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
145
-
146
- expect(result).toMatchObject({ error: "not_found" });
147
- });
148
-
149
- it("returns validation_failed when frontmatter is bad", async () => {
150
- mockCheckLicense.mockResolvedValue({
151
- tier: "pro",
152
- plan: "pro",
153
- features: [],
154
- expiresAt: null,
155
- revoked: false,
156
- });
157
- const adapter = makeAdapter();
158
-
159
- const result = await ailkUpdatePage(
160
- { ...VALID_INPUT, page: { title: "Missing description" } },
161
- adapter,
162
- );
163
-
164
- expect(result).toMatchObject({ error: "validation_failed" });
165
- expect(adapter.updatePage).not.toHaveBeenCalled();
166
- });
167
-
168
- it("returns { page, version } on happy path", async () => {
169
- mockCheckLicense.mockResolvedValue({
170
- tier: "pro",
171
- plan: "pro",
172
- features: [],
173
- expiresAt: null,
174
- revoked: false,
175
- });
176
- const persistedPage = {
177
- type: "Product" as const,
178
- slug: "widget-pro",
179
- title: "Widget Pro v2",
180
- description: "Updated.",
181
- };
182
- const adapter = makeAdapter({
183
- updatePage: jest
184
- .fn()
185
- .mockResolvedValue({ page: persistedPage, version: "v2" }),
186
- });
187
-
188
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
189
-
190
- expect(result).toEqual({ page: persistedPage, version: "v2" });
191
- const callArgs = (adapter.updatePage as jest.Mock).mock.calls[0];
192
- expect(callArgs[0]).toBe("Product");
193
- expect(callArgs[1]).toBe("widget-pro");
194
- expect(callArgs[3]).toEqual({ ifMatch: "v1" });
195
- });
196
- });
@@ -1,96 +0,0 @@
1
- // oss: false
2
- /**
3
- * create_page — Paid-tier MCP tool for authoring a new content page.
4
- *
5
- * D14a Stage 1 Unit C — wires the McpServer create_page handler to the
6
- * ContentAdapter.createPage method (Stage 1 Unit A implements the MDX
7
- * adapter; tests inject a fake adapter).
8
- *
9
- * Surface contract: validates input via FrontmatterSchema.extend({ body }),
10
- * runs the Paid-tier license guard, calls adapter.createPage, and maps adapter
11
- * errors to canonical MCP error bodies (see apps/mcp/src/lib/errors.ts).
12
- *
13
- * Input shape mirrors the Stage 0 catalog entry in
14
- * packages/metadata/src/mcp-tool-catalog.ts. Catalog parses a permissive
15
- * outer shape (`page: record`); this handler parses strictly via
16
- * FrontmatterSchema so bad frontmatter surfaces as a validation_failed body.
17
- */
18
-
19
- import { join } from "node:path";
20
-
21
- import {
22
- FrontmatterSchema,
23
- createMdxAdapter,
24
- mapAdapterError,
25
- type ContentAdapter,
26
- } from "@working-theory/content-adapters";
27
- import { z } from "zod";
28
-
29
- import { validationError, type ErrorBody } from "../lib/errors.js";
30
- import { requirePaidTier } from "../lib/require-paid-tier.js";
31
-
32
- const CreatePageInputSchema = FrontmatterSchema.extend({
33
- body: z.string().min(1),
34
- });
35
-
36
- interface CreatePageSuccess {
37
- page: Record<string, unknown>;
38
- version: string;
39
- }
40
-
41
- export type CreatePageResult = CreatePageSuccess | ErrorBody;
42
-
43
- export interface CreatePageInput {
44
- type: string;
45
- slug: string;
46
- page: Record<string, unknown>;
47
- body: string;
48
- }
49
-
50
- /**
51
- * ailkCreatePage handler.
52
- *
53
- * Optional `adapter` injection mirrors the Q3 resolution in the resource plan
54
- * (tests construct adapters directly with isolated content roots). When
55
- * omitted, falls back to the default MDX adapter rooted at process.cwd()/content.
56
- */
57
- export async function ailkCreatePage(
58
- input: CreatePageInput,
59
- adapter?: ContentAdapter,
60
- ): Promise<CreatePageResult> {
61
- const guard = await requirePaidTier();
62
- if (guard) return guard;
63
-
64
- const parsed = CreatePageInputSchema.safeParse({
65
- ...input.page,
66
- type: input.type,
67
- slug: input.slug,
68
- body: input.body,
69
- });
70
-
71
- if (!parsed.success) {
72
- return validationError("Invalid frontmatter or body for create_page.", {
73
- issues: parsed.error.issues,
74
- });
75
- }
76
-
77
- const resolved =
78
- adapter ??
79
- createMdxAdapter({ contentRoot: join(process.cwd(), "content") });
80
-
81
- try {
82
- const result = await resolved.createPage(
83
- parsed.data.type,
84
- parsed.data.slug,
85
- parsed.data,
86
- );
87
- return { page: result.page, version: result.version };
88
- } catch (err) {
89
- const mapped = mapAdapterError(err, {
90
- type: parsed.data.type,
91
- slug: parsed.data.slug,
92
- });
93
- if (mapped) return mapped;
94
- throw err;
95
- }
96
- }