create-ailk 0.2.0 → 0.4.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 (292) hide show
  1. package/component-catalog.md +50 -53
  2. package/dist/cli.js +19 -9
  3. package/dist/lib/apply-module-patches.d.ts +47 -0
  4. package/dist/lib/apply-module-patches.js +370 -0
  5. package/dist/lib/extract-module-bundle.d.ts +51 -3
  6. package/dist/lib/extract-module-bundle.js +327 -28
  7. package/dist/lib/fetch-module.d.ts +3 -38
  8. package/dist/lib/fetch-module.js +26 -8
  9. package/dist/lib/fetch-template.d.ts +20 -0
  10. package/dist/lib/fetch-template.js +129 -0
  11. package/dist/lib/module-license-gate.d.ts +10 -0
  12. package/dist/lib/module-license-gate.js +13 -4
  13. package/dist/lib/paid-paths.d.ts +16 -0
  14. package/dist/lib/paid-paths.js +90 -0
  15. package/dist/module-architecture.d.ts +197 -23
  16. package/dist/module-architecture.js +820 -25
  17. package/dist/parse-args.d.ts +9 -2
  18. package/dist/parse-args.js +46 -1
  19. package/dist/programmatic.d.ts +28 -2
  20. package/dist/programmatic.js +115 -8
  21. package/dist/surfaces.d.ts +12 -36
  22. package/dist/surfaces.js +77 -5
  23. package/dist/sync-routes.js +31 -1
  24. package/dist/template-manifest.d.ts +92 -0
  25. package/dist/template-manifest.js +168 -0
  26. package/dist/template-names.d.ts +41 -0
  27. package/dist/template-names.js +44 -0
  28. package/package.json +3 -2
  29. package/site-templates/marketing-site-free/ailk-template.json +8 -0
  30. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +103 -0
  31. package/site-templates/marketing-site-free/content/en/PrivacyPage/privacy.mdx +79 -0
  32. package/site-templates/marketing-site-free/content/en/TermsPage/terms.mdx +80 -0
  33. package/site-templates/marketing-site-free/provenance.json +4 -0
  34. package/templates/.claude/agents/web.md +2 -3
  35. package/templates/.claude/rules/architecture.md +5 -5
  36. package/templates/.claude/skills/README.md +2 -1
  37. package/templates/.env.example +21 -9
  38. package/templates/CONVENTIONS.md +6 -7
  39. package/templates/apps/api/.env.example +9 -0
  40. package/templates/apps/api/CLAUDE.md +33 -5
  41. package/templates/apps/api/package.json +0 -1
  42. package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
  43. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
  44. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
  45. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +166 -0
  46. package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
  47. package/templates/apps/api/src/config/index.ts +19 -0
  48. package/templates/apps/api/src/config/modules.ts +12 -14
  49. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  50. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
  51. package/templates/apps/api/src/lib/slice-load.ts +90 -0
  52. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
  53. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
  54. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
  55. package/templates/apps/api/src/openapi/index.ts +9 -3
  56. package/templates/apps/api/src/openapi/spec.ts +505 -65
  57. package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
  58. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
  59. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
  60. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
  61. package/templates/apps/api/src/routes/content/index.ts +38 -11
  62. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +17 -0
  63. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -0
  64. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
  65. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +10 -0
  66. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
  67. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
  68. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +10 -0
  69. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
  70. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
  71. package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
  72. package/templates/apps/api/src/routes/project-listings/me.ts +10 -0
  73. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
  74. package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
  75. package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
  76. package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
  77. package/templates/apps/api/src/routes/project-listings/start.ts +30 -0
  78. package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
  79. package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
  80. package/templates/apps/api/src/server.ts +504 -137
  81. package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
  82. package/templates/apps/api/src/services/listing-config.ts +58 -0
  83. package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
  84. package/templates/apps/api/src/services/project-listings.ts +229 -4
  85. package/templates/apps/api/src/vercel-handler.ts +60 -26
  86. package/templates/apps/mcp/.env.example +8 -0
  87. package/templates/apps/mcp/CLAUDE.md +2 -2
  88. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
  89. package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
  90. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
  91. package/templates/apps/mcp/src/config/modules.ts +14 -13
  92. package/templates/apps/mcp/src/server.ts +8 -4
  93. package/templates/apps/mcp/src/tools/index.ts +7 -22
  94. package/templates/apps/web/.env.example +15 -0
  95. package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
  96. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
  97. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
  98. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
  99. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
  100. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
  101. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
  102. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
  103. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
  104. package/templates/apps/web/app/[locale]/pricing/page.tsx +40 -0
  105. package/templates/apps/web/app/[locale]/privacy/page.tsx +40 -0
  106. package/templates/apps/web/app/[locale]/terms/page.tsx +40 -0
  107. package/templates/apps/web/app/llms.txt/route.ts +12 -9
  108. package/templates/apps/web/jest.config.cjs +19 -13
  109. package/templates/apps/web/lib/__tests__/server-api.test.ts +61 -0
  110. package/templates/apps/web/lib/server-api.ts +28 -12
  111. package/templates/apps/web/messages/en.json +58 -0
  112. package/templates/apps/web/next.config.mjs +1 -3
  113. package/templates/apps/web/package.json +0 -6
  114. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  115. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  116. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  117. package/templates/apps/web/public/favicon-16x16.png +0 -0
  118. package/templates/apps/web/public/favicon-32x32.png +0 -0
  119. package/templates/apps/web/public/favicon.ico +0 -0
  120. package/templates/apps/web/public/favicon.svg +6 -3
  121. package/templates/apps/web/public/lockup-horizontal.svg +4 -0
  122. package/templates/apps/web/public/logomark.svg +4 -0
  123. package/templates/apps/web/public/site.webmanifest +2 -2
  124. package/templates/apps/web/public/wordmark.svg +4 -0
  125. package/templates/database/CHANGELOG.md +47 -0
  126. package/templates/database/inbox/schema.prisma +67 -0
  127. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
  128. package/templates/database/package.json +1 -1
  129. package/templates/package.json +1 -1
  130. package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
  131. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
  132. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
  133. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
  134. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
  135. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
  136. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
  137. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
  138. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
  139. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
  140. package/templates/apps/api/src/__tests__/server.test.ts +0 -253
  141. package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
  142. package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
  143. package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
  144. package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
  145. package/templates/apps/api/src/bin/seed-presets.ts +0 -58
  146. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
  147. package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
  148. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
  149. package/templates/apps/api/src/lib/stripe.ts +0 -52
  150. package/templates/apps/api/src/lib/tenant-db.ts +0 -225
  151. package/templates/apps/api/src/lib/ws-token.ts +0 -91
  152. package/templates/apps/api/src/middleware/tenant.ts +0 -99
  153. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
  154. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
  155. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
  156. package/templates/apps/api/src/routes/billing/index.ts +0 -36
  157. package/templates/apps/api/src/routes/billing/portal.ts +0 -182
  158. package/templates/apps/api/src/routes/billing/read.ts +0 -75
  159. package/templates/apps/api/src/routes/billing/usage.ts +0 -153
  160. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
  161. package/templates/apps/api/src/routes/checkout/index.ts +0 -13
  162. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
  163. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
  164. package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
  165. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
  166. package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
  167. package/templates/apps/api/src/routes/flows/README.md +0 -147
  168. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
  169. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
  170. package/templates/apps/api/src/routes/flows/index.ts +0 -202
  171. package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
  172. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
  173. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +0 -221
  174. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
  175. package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
  176. package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
  177. package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
  178. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
  179. package/templates/apps/api/src/routes/schedule/index.ts +0 -249
  180. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
  181. package/templates/apps/api/src/routes/slack/actions.ts +0 -177
  182. package/templates/apps/api/src/routes/slack/index.ts +0 -38
  183. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
  184. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
  185. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
  186. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
  187. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
  188. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
  189. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
  190. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
  191. package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
  192. package/templates/apps/api/src/routes/webhooks/README.md +0 -80
  193. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
  194. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
  195. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
  196. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
  197. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
  198. package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
  199. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
  200. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
  201. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
  202. package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
  203. package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
  204. package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
  205. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
  206. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
  207. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
  208. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
  209. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +0 -684
  210. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
  211. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
  212. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
  213. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
  214. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
  215. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
  216. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
  217. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
  218. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
  219. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
  220. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
  221. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
  222. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
  223. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
  224. package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
  225. package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
  226. package/templates/apps/api/src/services/flow-engine.ts +0 -1296
  227. package/templates/apps/api/src/services/lead-promotion.ts +0 -176
  228. package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
  229. package/templates/apps/api/src/services/listing-promotion.ts +0 -342
  230. package/templates/apps/api/src/services/playbook-compile.ts +0 -398
  231. package/templates/apps/api/src/services/playbook-render.ts +0 -263
  232. package/templates/apps/api/src/services/project-listing-decision.ts +0 -510
  233. package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
  234. package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
  235. package/templates/apps/api/src/services/recommender-capture.ts +0 -835
  236. package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
  237. package/templates/apps/api/src/services/scheduling/index.ts +0 -63
  238. package/templates/apps/api/src/services/scheduling/types.ts +0 -88
  239. package/templates/apps/api/src/services/tenant-context.ts +0 -451
  240. package/templates/apps/api/src/services/usage-metering.ts +0 -699
  241. package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
  242. package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
  243. package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
  244. package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
  245. package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
  246. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
  247. package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
  248. package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
  249. package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
  250. package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
  251. package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
  252. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
  253. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
  254. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
  255. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
  256. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
  257. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
  258. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
  259. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
  260. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
  261. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
  262. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
  263. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
  264. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
  265. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
  266. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
  267. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
  268. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
  269. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
  270. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
  271. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
  272. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
  273. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
  274. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
  275. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
  276. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
  277. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
  278. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
  279. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
  280. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
  281. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
  282. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
  283. package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
  284. package/templates/apps/web/content/en/blog/README.txt +0 -25
  285. package/templates/apps/web/content/en/docs/index.mdx +0 -28
  286. package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
  287. package/templates/apps/web/lib/blog-list-data.ts +0 -95
  288. package/templates/apps/web/lib/blog-post-data.ts +0 -79
  289. package/templates/apps/web/lib/source.ts +0 -34
  290. package/templates/apps/web/public/logomark-dark.svg +0 -4
  291. package/templates/apps/web/public/logomark-light.svg +0 -4
  292. package/templates/apps/web/source.config.ts +0 -78
@@ -1,166 +0,0 @@
1
- /**
2
- * @file waitlist-scoring.ts
3
- * @description The Prisma-touching glue for the rules-layer scoring engine —
4
- * A2 (#4662 D6).
5
- *
6
- * `scoreCompletedSignup` is the ONE `prisma.waitlistScore.upsert` write path
7
- * in `apps/api` — one row per (lead, EXPERIMENT), re-scoring overwrites,
8
- * never appends (A1 D3, scoped per experiment by #5036 D6d: a visitor who
9
- * joins two founders' waitlists is one lead with two scores, and a
10
- * lead-only key let the second silently erase the first). It loads the owning `WaitlistExperiment`, parses its config,
11
- * evaluates via the pure `@working-theory/validation` evaluator, and
12
- * persists the result. Every failure mode (no experiment row, an invalid
13
- * config, an evaluator throw, a Prisma error) returns a typed outcome — it
14
- * never throws to its caller (`flow-engine.ts`'s `completeSubmission`, which
15
- * wraps the call in its own fail-soft try/catch as a residual net).
16
- *
17
- * PII boundary: logs carry only `{siteId, experimentKey, leadId, kind}` —
18
- * never an answer value or the assembled payload (mirrors
19
- * `waitlist-experiments.ts` / `flow-engine.ts`'s boundary).
20
- */
21
-
22
- import type { Prisma } from "@working-theory/database";
23
- import { logger } from "@working-theory/observability";
24
- import {
25
- answersFromPayload,
26
- evaluateWaitlistScoring,
27
- waitlistExperimentConfigSchema,
28
- type WaitlistFlags,
29
- } from "@working-theory/validation";
30
-
31
- import { prisma } from "../lib/prisma.js";
32
-
33
- import { compileAndStorePlaybook, TARGETING_MATURITY_EXPERIMENT_KEY } from "./playbook-compile.js";
34
-
35
- // ─── Types ────────────────────────────────────────────────────────────────────
36
-
37
- export type ScoreCompletedSignupInput = {
38
- siteId: string;
39
- leadId: string;
40
- experimentKey: string;
41
- waitlistKey: string;
42
- payload: Record<string, unknown>;
43
- flags?: WaitlistFlags;
44
- };
45
-
46
- export type ScoreCompletedSignupSkipReason = "no_experiment" | "no_scoring";
47
- export type ScoreCompletedSignupErrorKind =
48
- | "invalid_config"
49
- | "evaluation_failed"
50
- | "persist_failed";
51
-
52
- export type ScoreCompletedSignupResult =
53
- | { ok: true; tier: string }
54
- | { ok: true; skipped: ScoreCompletedSignupSkipReason }
55
- | { ok: false; error: { kind: ScoreCompletedSignupErrorKind } };
56
-
57
- function errKindOf(err: unknown): string {
58
- return err instanceof Error ? err.constructor.name : typeof err;
59
- }
60
-
61
- // ─── scoreCompletedSignup — the ONE write path (D6) ─────────────────────────
62
-
63
- /**
64
- * Load the `[siteId, experimentKey]` `WaitlistExperiment`, evaluate the
65
- * signup's answers against its `scoring` register (when declared), and
66
- * upsert exactly one `WaitlistScore` row on `[leadId]`.
67
- */
68
- export async function scoreCompletedSignup(
69
- input: ScoreCompletedSignupInput,
70
- ): Promise<ScoreCompletedSignupResult> {
71
- const { siteId, leadId, experimentKey, waitlistKey, payload, flags } = input;
72
-
73
- const row = await prisma.waitlistExperiment.findUnique({
74
- where: { siteId_key: { siteId, key: experimentKey } },
75
- });
76
- if (!row) {
77
- return { ok: true, skipped: "no_experiment" };
78
- }
79
-
80
- const parsed = waitlistExperimentConfigSchema.safeParse(row.config);
81
- if (!parsed.success) {
82
- logger.error(
83
- { siteId, experimentKey, leadId, kind: "invalid_config" },
84
- "[waitlist-scoring] experiment config failed validation (defensive — the row was validated on write)",
85
- );
86
- return { ok: false, error: { kind: "invalid_config" } };
87
- }
88
- const config = parsed.data;
89
- if (!config.scoring) {
90
- return { ok: true, skipped: "no_scoring" };
91
- }
92
-
93
- const answers = answersFromPayload(config.questionSet, payload);
94
- let evaluation;
95
- try {
96
- evaluation = evaluateWaitlistScoring(config.scoring, answers, flags ?? {});
97
- } catch (err) {
98
- logger.error(
99
- { siteId, experimentKey, leadId, kind: "evaluation_failed", errKind: errKindOf(err) },
100
- "[waitlist-scoring] evaluation failed",
101
- );
102
- return { ok: false, error: { kind: "evaluation_failed" } };
103
- }
104
-
105
- let scoreRow;
106
- try {
107
- scoreRow = await prisma.waitlistScore.upsert({
108
- where: { leadId_experimentKey: { leadId, experimentKey } },
109
- create: {
110
- siteId,
111
- leadId,
112
- experimentKey,
113
- waitlistKey,
114
- tier: evaluation.tier,
115
- score: evaluation.score,
116
- categoryScores: evaluation.categoryScores as unknown as Prisma.InputJsonValue,
117
- ruleTrace: evaluation.ruleTrace as unknown as Prisma.InputJsonValue,
118
- },
119
- update: {
120
- tier: evaluation.tier,
121
- score: evaluation.score,
122
- categoryScores: evaluation.categoryScores as unknown as Prisma.InputJsonValue,
123
- ruleTrace: evaluation.ruleTrace as unknown as Prisma.InputJsonValue,
124
- },
125
- });
126
- } catch (err) {
127
- logger.error(
128
- { siteId, experimentKey, leadId, kind: "persist_failed", errKind: errKindOf(err) },
129
- "[waitlist-scoring] persist failed",
130
- );
131
- return { ok: false, error: { kind: "persist_failed" } };
132
- }
133
-
134
- // FEAT-074 U4a (#4715): the compile is the next link in this ONE write
135
- // path (D4) — gated on experimentKey, fail-soft internally
136
- // (compileAndStorePlaybook never throws by contract). The try/catch here
137
- // is the residual safety net (mirrors flow-engine.ts's own "swallowed by
138
- // design" fulfillment guard) — the score row above is ALREADY persisted,
139
- // so a genuine escape here must never turn this already-successful score
140
- // into a thrown failure this function's own header promises it never does.
141
- if (experimentKey === TARGETING_MATURITY_EXPERIMENT_KEY) {
142
- try {
143
- await compileAndStorePlaybook({
144
- siteId,
145
- leadId,
146
- experimentKey,
147
- scoreId: scoreRow.id,
148
- tier: evaluation.tier,
149
- ruleTrace: evaluation.ruleTrace,
150
- });
151
- } catch (err) {
152
- logger.error(
153
- {
154
- siteId,
155
- experimentKey,
156
- leadId,
157
- kind: "playbook_compile_escaped",
158
- errKind: errKindOf(err),
159
- },
160
- "[waitlist-scoring] compileAndStorePlaybook escaped its own fail-soft catch — the score is unaffected",
161
- );
162
- }
163
- }
164
-
165
- return { ok: true, tier: evaluation.tier };
166
- }
@@ -1,165 +0,0 @@
1
- /**
2
- * @file waitlist-signups.ts
3
- * @description The respondent read (#4731 D2/D3/D4) — `readMySignup`, the
4
- * ONLY read path behind `GET /v1/waitlist-signups/me`: a signed-in,
5
- * email-verified user reads their OWN newest waitlist signup for one
6
- * experiment. Nothing here enumerates, nothing accepts an email, nothing
7
- * reads across sites.
8
- *
9
- * Mirrors `lead-promotion.ts` (ADR 0009 D3 service-layer pattern): the same
10
- * `User {id, email, emailVerified}` read, the same verified-email refusal
11
- * (D2 there — the consent basis for linking anonymous-lead PII to a user;
12
- * here a hard 403, evaluated BEFORE any Lead read), and a scalar-only join
13
- * to the inbox namespace by email — `Lead` carries no `@relation` (ADR 0009
14
- * invariant #1), so the read is four narrow queries, each scoped to
15
- * `currentSiteId()` (#4665 D3: the scope is a parameter of every query,
16
- * never a post-filter).
17
- *
18
- * Email normalization (#4731 D3, the contract's open question): the
19
- * respondent email is persisted AS TYPED — `flow-engine.ts`'s
20
- * `extractMappedEmail` and `lead-routing.ts`'s `persistToDb` neither trim
21
- * nor lower-case it (the boundary validator `z.string().email()` already
22
- * rejects surrounding whitespace). So the match is `lower(trim())` on both
23
- * sides: `equals` on the trimmed, lower-cased user email with
24
- * `mode: "insensitive"` for the stored value.
25
- *
26
- * PII boundary: logs carry only `{ siteId, experimentKey, leadId, kind }` —
27
- * never email, payload, or an answer value.
28
- */
29
-
30
- import { logger } from "@working-theory/observability";
31
- import {
32
- aeoScoreResultSchema,
33
- type WaitlistSignupMeResponse,
34
- } from "@working-theory/validation";
35
-
36
- import { prisma } from "../lib/prisma.js";
37
- import { currentSiteId } from "../lib/site.js";
38
-
39
- // ─── Types ────────────────────────────────────────────────────────────────────
40
-
41
- export type ReadMySignupResult =
42
- | { ok: true; value: WaitlistSignupMeResponse }
43
- | {
44
- ok: false;
45
- error: {
46
- kind: "not_found_user" | "email_unverified" | "signup_not_found";
47
- message: string;
48
- };
49
- };
50
-
51
- // ─── Helpers ──────────────────────────────────────────────────────────────────
52
-
53
- /** A string-valued payload key, or undefined when absent / not a string. */
54
- function readPayloadString(payload: unknown, key: string): string | undefined {
55
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return undefined;
56
- const value = (payload as Record<string, unknown>)[key];
57
- return typeof value === "string" ? value : undefined;
58
- }
59
-
60
- /** A schema-defensive read of the stored `FlowSession.aeoScore` JSON value (mirrors flow-engine.ts). */
61
- function readAeoScore(value: unknown): WaitlistSignupMeResponse["aeoScore"] {
62
- if (value === null || value === undefined) return null;
63
- const parsed = aeoScoreResultSchema.safeParse(value);
64
- return parsed.success ? parsed.data : null;
65
- }
66
-
67
- const SIGNUP_NOT_FOUND: ReadMySignupResult = {
68
- ok: false,
69
- error: { kind: "signup_not_found", message: "Signup not found" },
70
- };
71
-
72
- // ─── Main export ──────────────────────────────────────────────────────────────
73
-
74
- /**
75
- * Read the calling user's newest signup for `experimentKey` at the current
76
- * site. `userId` is the auth preHandler's `request.userId` — the ONLY
77
- * identity input; `experimentKey` is boundary-validated by the route.
78
- *
79
- * Result kinds (the route maps them to HTTP):
80
- * - `not_found_user` — no `User` row for `userId` (404 on the wire).
81
- * - `email_unverified` — `User.emailVerified !== true`; refused BEFORE any
82
- * Lead read (403).
83
- * - `signup_not_found` — no `Lead` at this site with this user's email and
84
- * this `experimentKey` (or a corrupt row missing
85
- * `waitlistKey`) (404).
86
- */
87
- export async function readMySignup(
88
- userId: string,
89
- experimentKey: string,
90
- ): Promise<ReadMySignupResult> {
91
- const siteId = currentSiteId();
92
-
93
- // D2 — the exact read lead-promotion.ts performs, mirrored not forked.
94
- const user = await prisma.user.findUnique({
95
- where: { id: userId },
96
- select: { id: true, email: true, emailVerified: true },
97
- });
98
- if (!user) {
99
- return { ok: false, error: { kind: "not_found_user", message: "User not found" } };
100
- }
101
-
102
- // D2 — the verified-email gate is a service-level invariant: an unverified
103
- // address never reaches a Lead read, whatever the call site.
104
- if (user.emailVerified !== true) {
105
- return {
106
- ok: false,
107
- error: { kind: "email_unverified", message: "Verify your email to view your signup" },
108
- };
109
- }
110
-
111
- // D3 — lower(trim()) on both sides; D4 — scoped, newest wins.
112
- const lead = await prisma.lead.findFirst({
113
- where: {
114
- siteId,
115
- email: { equals: user.email.trim().toLowerCase(), mode: "insensitive" },
116
- payload: { path: ["experimentKey"], equals: experimentKey },
117
- },
118
- orderBy: { createdAt: "desc" },
119
- select: { id: true, payload: true, createdAt: true },
120
- });
121
- if (!lead) return SIGNUP_NOT_FOUND;
122
-
123
- // `waitlistKey` is server-written LAST beside `experimentKey` (flow-engine
124
- // D4) — a lead carrying one without the other is corrupt data, treated as
125
- // not-found rather than emitting an out-of-contract value (the
126
- // waitlist-dashboard.ts `getSignup` posture, PR #4687).
127
- const waitlistKey = readPayloadString(lead.payload, "waitlistKey");
128
- if (!waitlistKey) {
129
- logger.warn(
130
- { siteId, experimentKey, leadId: lead.id, kind: "missing_waitlist_key" },
131
- "[waitlist-signups] signup missing waitlistKey — treating as not_found",
132
- );
133
- return SIGNUP_NOT_FOUND;
134
- }
135
-
136
- // D4 — `@@unique([leadId, experimentKey])` (#5036 D6d); the scope ANDed in,
137
- // never a post-filter.
138
- const scoreRow = await prisma.waitlistScore.findFirst({
139
- where: { leadId: lead.id, siteId, experimentKey },
140
- select: { tier: true, score: true },
141
- });
142
-
143
- // D4 — the completing session (leadId is stamped in the same completion
144
- // call that created the lead). Fallback to Lead.createdAt if absent (D5).
145
- const session = await prisma.flowSession.findFirst({
146
- where: { leadId: lead.id, siteId, completedAt: { not: null } },
147
- orderBy: { completedAt: "desc" },
148
- select: { aeoScore: true, completedAt: true },
149
- });
150
-
151
- // D5 — seven fields, nothing more. `waitlistKey` and `q3` are the ONLY
152
- // payload keys that leave the row.
153
- return {
154
- ok: true,
155
- value: {
156
- experimentKey,
157
- waitlistKey,
158
- completedAt: (session?.completedAt ?? lead.createdAt).toISOString(),
159
- tier: scoreRow?.tier ?? null,
160
- score: scoreRow?.score ?? null,
161
- aeoScore: readAeoScore(session?.aeoScore),
162
- answers: { q3: readPayloadString(lead.payload, "q3") ?? null },
163
- },
164
- };
165
- }
@@ -1,242 +0,0 @@
1
- // oss: false
2
- /**
3
- * Tests for the list_availability + get_event_meta + create_booking MCP
4
- * tools (#4416, #4633).
5
- *
6
- * The claim these tools make is PARITY OF CODE, not just of endpoint: an
7
- * agent that hits a taken slot must read `slot_unavailable`, the same word
8
- * the route emitted and the same word the booker section branches on. Most of
9
- * what follows pins that mapping.
10
- */
11
-
12
- import { ailkCreateBooking } from "../src/tools/create_booking.js";
13
- import { ailkGetEventMeta } from "../src/tools/get_event_meta.js";
14
- import {
15
- ailkListAvailability,
16
- schedulingKindFromStatus,
17
- } from "../src/tools/list_availability.js";
18
-
19
- jest.mock("@working-theory/api-client", () => ({
20
- apiClient: {
21
- schedule: {
22
- availability: jest.fn(),
23
- eventMeta: jest.fn(),
24
- book: jest.fn(),
25
- },
26
- },
27
- }));
28
-
29
- const { apiClient } = require("@working-theory/api-client") as {
30
- apiClient: {
31
- schedule: { availability: jest.Mock; eventMeta: jest.Mock; book: jest.Mock };
32
- };
33
- };
34
-
35
- const VALID_RANGE = {
36
- calLink: "sam/fit-check",
37
- from: "2026-08-26T00:00:00.000Z",
38
- to: "2026-09-02T00:00:00.000Z",
39
- };
40
-
41
- const AVAILABILITY = {
42
- event: {
43
- title: "15-Minute Call with Sam",
44
- durationMinutes: 15,
45
- meetingType: "Google Meet",
46
- timeZone: "America/Denver",
47
- },
48
- slots: [
49
- { start: "2026-08-27T16:00:00.000Z", end: "2026-08-27T16:15:00.000Z" },
50
- ],
51
- };
52
-
53
- beforeEach(() => {
54
- jest.clearAllMocks();
55
- });
56
-
57
- describe("schedulingKindFromStatus", () => {
58
- it.each([
59
- [400, "invalid_input"],
60
- [404, "event_not_found"],
61
- [409, "slot_unavailable"],
62
- [503, "not_configured"],
63
- [502, "provider_error"],
64
- [500, "provider_error"],
65
- ])("maps %i to %s", (status, kind) => {
66
- expect(schedulingKindFromStatus(status as number)).toBe(kind);
67
- });
68
- });
69
-
70
- describe("ailkListAvailability", () => {
71
- it("rejects a malformed calLink without calling api-client", async () => {
72
- const result = await ailkListAvailability({
73
- ...VALID_RANGE,
74
- calLink: "https://cal.com/sam/fit-check",
75
- });
76
-
77
- expect(result.ok).toBe(false);
78
- if (!result.ok) expect(result.error.kind).toBe("invalid_input");
79
- expect(apiClient.schedule.availability).not.toHaveBeenCalled();
80
- });
81
-
82
- it("rejects a range wider than the 60-day cap without calling api-client", async () => {
83
- const result = await ailkListAvailability({
84
- ...VALID_RANGE,
85
- to: "2027-08-26T00:00:00.000Z",
86
- });
87
-
88
- expect(result.ok).toBe(false);
89
- expect(apiClient.schedule.availability).not.toHaveBeenCalled();
90
- });
91
-
92
- it("forwards the validated range and returns the derived event meta", async () => {
93
- apiClient.schedule.availability.mockResolvedValueOnce({
94
- ok: true,
95
- body: AVAILABILITY,
96
- });
97
-
98
- const result = await ailkListAvailability(VALID_RANGE);
99
-
100
- expect(apiClient.schedule.availability).toHaveBeenCalledWith(VALID_RANGE);
101
- expect(result.ok).toBe(true);
102
- if (result.ok) {
103
- expect(result.data.event.durationMinutes).toBe(15);
104
- expect(result.data.slots).toHaveLength(1);
105
- }
106
- });
107
-
108
- it("surfaces an unconfigured deploy as not_configured, not a bare status", async () => {
109
- apiClient.schedule.availability.mockResolvedValueOnce({
110
- ok: false,
111
- status: 503,
112
- body: null,
113
- });
114
-
115
- const result = await ailkListAvailability(VALID_RANGE);
116
-
117
- expect(result.ok).toBe(false);
118
- if (!result.ok) expect(result.error.kind).toBe("not_configured");
119
- });
120
- });
121
-
122
- describe("ailkGetEventMeta", () => {
123
- const EVENTS = {
124
- events: [
125
- {
126
- calLink: "sam/fit-check",
127
- event: {
128
- title: "15-Minute Call with Sam",
129
- durationMinutes: 15,
130
- price: { amountMinor: 0, currency: "usd" },
131
- timeZone: "America/Denver",
132
- },
133
- },
134
- {
135
- calLink: "sam/power-hour",
136
- event: {
137
- title: "Power Hour",
138
- durationMinutes: 60,
139
- description: "Sixty focused minutes on your positioning.",
140
- price: { amountMinor: 25000, currency: "usd" },
141
- timeZone: "America/Denver",
142
- },
143
- },
144
- ],
145
- };
146
-
147
- it("rejects a malformed link in the list without calling api-client", async () => {
148
- const result = await ailkGetEventMeta({
149
- calLinks: "sam/fit-check,https://cal.com/sam/power-hour",
150
- });
151
-
152
- expect(result.ok).toBe(false);
153
- if (!result.ok) expect(result.error.kind).toBe("invalid_input");
154
- expect(apiClient.schedule.eventMeta).not.toHaveBeenCalled();
155
- });
156
-
157
- it("forwards the links and returns meta only — a free and a priced event", async () => {
158
- apiClient.schedule.eventMeta.mockResolvedValueOnce({
159
- ok: true,
160
- body: EVENTS,
161
- });
162
-
163
- const result = await ailkGetEventMeta({
164
- calLinks: "sam/fit-check,sam/power-hour",
165
- });
166
-
167
- expect(apiClient.schedule.eventMeta).toHaveBeenCalledWith({
168
- calLinks: "sam/fit-check,sam/power-hour",
169
- });
170
- expect(result.ok).toBe(true);
171
- if (result.ok) {
172
- expect(result.data.events).toHaveLength(2);
173
- expect(result.data.events[0]?.event.price?.amountMinor).toBe(0);
174
- expect(result.data.events[1]?.event.price?.amountMinor).toBe(25000);
175
- expect(result.data.events[1]?.event.description).toBe(
176
- "Sixty focused minutes on your positioning.",
177
- );
178
- }
179
- });
180
-
181
- it("surfaces an unconfigured deploy as not_configured, not a bare status", async () => {
182
- apiClient.schedule.eventMeta.mockResolvedValueOnce({
183
- ok: false,
184
- status: 503,
185
- body: null,
186
- });
187
-
188
- const result = await ailkGetEventMeta({ calLinks: "sam/fit-check" });
189
-
190
- expect(result.ok).toBe(false);
191
- if (!result.ok) expect(result.error.kind).toBe("not_configured");
192
- });
193
- });
194
-
195
- describe("ailkCreateBooking", () => {
196
- const VALID_BOOKING = {
197
- calLink: "sam/fit-check",
198
- start: "2026-08-27T16:00:00.000Z",
199
- attendee: { name: "Sam Rivera", email: "sam@example.com" },
200
- };
201
-
202
- it("rejects a malformed attendee email without calling api-client", async () => {
203
- const result = await ailkCreateBooking({
204
- ...VALID_BOOKING,
205
- attendee: { name: "Sam Rivera", email: "sam@" },
206
- });
207
-
208
- expect(result.ok).toBe(false);
209
- if (!result.ok) expect(result.error.kind).toBe("invalid_input");
210
- expect(apiClient.schedule.book).not.toHaveBeenCalled();
211
- });
212
-
213
- it("forwards the booking and returns the provider's identifier", async () => {
214
- apiClient.schedule.book.mockResolvedValueOnce({
215
- ok: true,
216
- body: {
217
- bookingId: "bkg_9",
218
- start: "2026-08-27T16:00:00.000Z",
219
- end: "2026-08-27T16:15:00.000Z",
220
- },
221
- });
222
-
223
- const result = await ailkCreateBooking(VALID_BOOKING);
224
-
225
- expect(apiClient.schedule.book).toHaveBeenCalledWith(VALID_BOOKING);
226
- expect(result.ok).toBe(true);
227
- if (result.ok) expect(result.data.bookingId).toBe("bkg_9");
228
- });
229
-
230
- it("surfaces a lost race as slot_unavailable so the caller can retry another slot", async () => {
231
- apiClient.schedule.book.mockResolvedValueOnce({
232
- ok: false,
233
- status: 409,
234
- body: null,
235
- });
236
-
237
- const result = await ailkCreateBooking(VALID_BOOKING);
238
-
239
- expect(result.ok).toBe(false);
240
- if (!result.ok) expect(result.error.kind).toBe("slot_unavailable");
241
- });
242
- });
@@ -1,58 +0,0 @@
1
- // oss: false
2
- /**
3
- * create_booking — book one open slot for an attendee (#4416).
4
- *
5
- * The MCP twin of `POST /v1/schedule/book` (API↔MCP parity, root CLAUDE.md).
6
- * Validates input via Zod, forwards through `@working-theory/api-client`, and
7
- * shapes the response into the MCP output schema. No business logic and no
8
- * credential — the scheduling key lives in apps/api.
9
- *
10
- * MCP-only anonymous write, the posture `subscribe_newsletter` and
11
- * `request_callback` already established: an agent booking on a prospect's
12
- * behalf has no session. Listed in `MCP_ONLY_TOOLS` in
13
- * apps/mcp/__tests__/parity.test.ts.
14
- *
15
- * `slot_unavailable` is the interesting failure and is surfaced verbatim: a
16
- * caller that races another booker should re-read availability and retry with
17
- * a different slot, exactly as the `booker` section's UI does.
18
- *
19
- * Input: { calLink, start, attendee: { name, email, timeZone?, notes? } }
20
- * Output: { ok: true, data: { bookingId, start, end, meetingUrl? } }
21
- * | { ok: false, error: { kind } }
22
- * Session/scope: none — anonymous, identity-blind (apps/mcp/CLAUDE.md).
23
- */
24
-
25
- import { apiClient } from "@working-theory/api-client";
26
- import {
27
- createBookingInputSchema,
28
- createBookingOutputSchema,
29
- type CreateBookingInput,
30
- type CreateBookingOutput,
31
- } from "@working-theory/validation";
32
-
33
- import { schedulingKindFromStatus } from "./list_availability.js";
34
-
35
- export { createBookingInputSchema, createBookingOutputSchema };
36
- export type { CreateBookingInput, CreateBookingOutput };
37
-
38
- export async function ailkCreateBooking(
39
- input: CreateBookingInput,
40
- ): Promise<CreateBookingOutput> {
41
- const parsed = createBookingInputSchema.safeParse(input);
42
- if (!parsed.success) {
43
- return {
44
- ok: false,
45
- error: {
46
- kind: "invalid_input",
47
- message: parsed.error.issues[0]?.message ?? "Invalid input.",
48
- },
49
- };
50
- }
51
-
52
- const res = await apiClient.schedule.book(parsed.data);
53
- if (!res.ok) {
54
- return { ok: false, error: { kind: schedulingKindFromStatus(res.status) } };
55
- }
56
-
57
- return createBookingOutputSchema.parse({ ok: true, data: res.body });
58
- }
@@ -1,55 +0,0 @@
1
- // oss: false
2
- /**
3
- * get_event_meta — read one or more events' derived meta, no slots (#4633).
4
- *
5
- * The MCP twin of `GET /v1/schedule/event-meta` (API↔MCP parity, root
6
- * CLAUDE.md). Exists for the same reason the route does: rendering an event's
7
- * facts (title, duration, description, price) must not cost an availability
8
- * window per event. Validates input via Zod, forwards through
9
- * `@working-theory/api-client`, and shapes the response into the MCP output
10
- * schema. No business logic and no credential — the scheduling key lives in
11
- * apps/api.
12
- *
13
- * MCP-only — no web Server Action twin, same posture as `list_availability`:
14
- * a tile-rendering page is an RSC calling the HTTP route via api-client, not
15
- * a form. Listed in `MCP_ONLY_TOOLS` in apps/mcp/__tests__/parity.test.ts.
16
- *
17
- * Input: { calLinks } — comma-separated `"<owner>/<event-slug>"` links
18
- * Output: { ok: true, data: { events } } | { ok: false, error: { kind } }
19
- * Session/scope: none — anonymous, identity-blind (apps/mcp/CLAUDE.md).
20
- */
21
-
22
- import { apiClient } from "@working-theory/api-client";
23
- import {
24
- getEventMetaInputSchema,
25
- getEventMetaOutputSchema,
26
- type GetEventMetaInput,
27
- type GetEventMetaOutput,
28
- } from "@working-theory/validation";
29
-
30
- import { schedulingKindFromStatus } from "./list_availability.js";
31
-
32
- export { getEventMetaInputSchema, getEventMetaOutputSchema };
33
- export type { GetEventMetaInput, GetEventMetaOutput };
34
-
35
- export async function ailkGetEventMeta(
36
- input: GetEventMetaInput,
37
- ): Promise<GetEventMetaOutput> {
38
- const parsed = getEventMetaInputSchema.safeParse(input);
39
- if (!parsed.success) {
40
- return {
41
- ok: false,
42
- error: {
43
- kind: "invalid_input",
44
- message: parsed.error.issues[0]?.message ?? "Invalid input.",
45
- },
46
- };
47
- }
48
-
49
- const res = await apiClient.schedule.eventMeta(parsed.data);
50
- if (!res.ok) {
51
- return { ok: false, error: { kind: schedulingKindFromStatus(res.status) } };
52
- }
53
-
54
- return getEventMetaOutputSchema.parse({ ok: true, data: res.body });
55
- }