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,392 +0,0 @@
1
- // oss: false
2
- /**
3
- * Cal.com provider (#4416) — the first implementation of the scheduling seam.
4
- *
5
- * Everything Cal-specific lives here and nowhere else: the per-endpoint
6
- * `cal-api-version` headers, the event-type lookup that turns a
7
- * `"<owner>/<event-slug>"` link into a numeric event-type id, the slot map
8
- * keyed by day, and the booking body. `types.ts` names none of it, so #4288's
9
- * self-hosted engine implements the same two methods without inheriting any
10
- * of Cal's vocabulary.
11
- *
12
- * CREDENTIALS ARE SERVER-SIDE ONLY. `CAL_API_KEY` is read here, inside
13
- * `apps/api`. The browser reaches Cal exclusively through this app's own
14
- * `/v1/schedule/*` routes; the key is never serialized into a section's data,
15
- * a page's props, or any response body.
16
- *
17
- * Cal v2 endpoints used (each pins its own API version, per Cal's docs):
18
- * GET /v2/event-types?username=&eventTypeSlug= cal-api-version: 2024-06-14
19
- * GET /v2/slots?eventTypeId=&start=&end=&timeZone= cal-api-version: 2024-09-04
20
- * POST /v2/bookings cal-api-version: 2024-08-13
21
- */
22
-
23
- import type {
24
- AvailabilityResult,
25
- BookingResult,
26
- SchedulingEventMeta,
27
- SchedulingPrice,
28
- SchedulingSlot,
29
- } from "@working-theory/validation";
30
-
31
- import {
32
- schedulingError,
33
- type CreateBookingRequest,
34
- type GetEventMetaRequest,
35
- type ListAvailabilityRequest,
36
- type SchedulingOutcome,
37
- type SchedulingProvider,
38
- } from "./types.js";
39
-
40
- const DEFAULT_BASE_URL = "https://api.cal.com";
41
- const EVENT_TYPES_API_VERSION = "2024-06-14";
42
- const SLOTS_API_VERSION = "2024-09-04";
43
- const BOOKINGS_API_VERSION = "2024-08-13";
44
-
45
- /** Cal answers in seconds-free ISO; anything else means the response drifted. */
46
- const REQUEST_TIMEOUT_MS = 10_000;
47
-
48
- export interface CalProviderOptions {
49
- apiKey: string;
50
- /** Overridable for tests and self-hosted Cal instances. */
51
- baseUrl?: string;
52
- /** Injectable for tests; defaults to the platform `fetch`. */
53
- fetchImpl?: typeof fetch;
54
- }
55
-
56
- /** `"sam/fit-check"` → `{ username: "sam", eventTypeSlug: "fit-check" }`. */
57
- function splitCalLink(
58
- calLink: string,
59
- ): { username: string; eventTypeSlug: string } | null {
60
- const parts = calLink.split("/");
61
- if (parts.length !== 2) return null;
62
- const [username, eventTypeSlug] = parts;
63
- if (!username || !eventTypeSlug) return null;
64
- return { username, eventTypeSlug };
65
- }
66
-
67
- /**
68
- * Cal describes an event's location as a small tagged union whose shape has
69
- * changed across versions. Rather than model every variant, read the one
70
- * field every variant carries and turn it into the words a booker shows
71
- * ("Google Meet", "Phone call") — falling back to no meeting type at all,
72
- * which the UI renders by simply omitting that meta item.
73
- */
74
- const LOCATION_LABELS: Record<string, string> = {
75
- integrations_google_meet: "Google Meet",
76
- "integrations:google:meet": "Google Meet",
77
- "integrations:daily": "Cal Video",
78
- integrations_daily: "Cal Video",
79
- "integrations:zoom": "Zoom",
80
- integrations_zoom: "Zoom",
81
- phone: "Phone call",
82
- userPhone: "Phone call",
83
- attendeeInPerson: "In person",
84
- inPerson: "In person",
85
- link: "Web link",
86
- };
87
-
88
- /**
89
- * Cal states an event's price as two sibling fields — `price` in minor units
90
- * (0 for every event on an account with no payment app connected) and
91
- * `currency` (`"usd"`). Free and unpriced are therefore the same state at the
92
- * source. Kept in minor units: converting to major units needs the currency's
93
- * decimal rules and the viewer's locale, which only the rendering site has.
94
- */
95
- function readPrice(eventType: Record<string, unknown>): SchedulingPrice | undefined {
96
- const amount = eventType["price"];
97
- const currency = eventType["currency"];
98
- if (typeof amount !== "number" || !Number.isInteger(amount) || amount < 0) {
99
- return undefined;
100
- }
101
- if (typeof currency !== "string" || !/^[A-Za-z]{3}$/.test(currency)) {
102
- return undefined;
103
- }
104
- return { amountMinor: amount, currency };
105
- }
106
-
107
- function readMeetingType(eventType: Record<string, unknown>): string | undefined {
108
- const locations = eventType["locations"];
109
- if (!Array.isArray(locations) || locations.length === 0) return undefined;
110
- const first = locations[0];
111
- if (typeof first !== "object" || first === null) return undefined;
112
- const raw = (first as Record<string, unknown>)["type"];
113
- if (typeof raw !== "string" || raw === "") return undefined;
114
- return LOCATION_LABELS[raw] ?? undefined;
115
- }
116
-
117
- /**
118
- * Cal's `/v2/slots` payload is a map of day → slot list, and the slot entries
119
- * have carried `start`/`time` across versions. Flatten it into the seam's
120
- * `{ start, end }` pairs, deriving `end` from the event's own duration so the
121
- * shape is identical no matter which key Cal used.
122
- */
123
- function flattenSlots(
124
- data: unknown,
125
- durationMinutes: number,
126
- ): SchedulingSlot[] {
127
- if (typeof data !== "object" || data === null) return [];
128
- const slots: SchedulingSlot[] = [];
129
- for (const daySlots of Object.values(data as Record<string, unknown>)) {
130
- if (!Array.isArray(daySlots)) continue;
131
- for (const entry of daySlots) {
132
- const start =
133
- typeof entry === "string"
134
- ? entry
135
- : typeof entry === "object" && entry !== null
136
- ? ((entry as Record<string, unknown>)["start"] ??
137
- (entry as Record<string, unknown>)["time"])
138
- : undefined;
139
- if (typeof start !== "string") continue;
140
- const startMs = Date.parse(start);
141
- if (Number.isNaN(startMs)) continue;
142
- slots.push({
143
- start: new Date(startMs).toISOString(),
144
- end: new Date(startMs + durationMinutes * 60_000).toISOString(),
145
- });
146
- }
147
- }
148
- slots.sort((a, b) => a.start.localeCompare(b.start));
149
- return slots;
150
- }
151
-
152
- /**
153
- * Build the Cal provider. A factory rather than a class, per this repo's
154
- * object discipline: adapters and clients are `createX()` functions, and
155
- * everything below closes over `options` instead of carrying instance state.
156
- */
157
- export function createCalSchedulingProvider(
158
- options: CalProviderOptions,
159
- ): SchedulingProvider {
160
- const apiKey = options.apiKey;
161
- const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
162
- const fetchImpl = options.fetchImpl ?? fetch;
163
-
164
- async function request(
165
- path: string,
166
- apiVersion: string,
167
- init?: RequestInit,
168
- ): Promise<SchedulingOutcome<unknown>> {
169
- const controller = new AbortController();
170
- const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
171
- try {
172
- const res = await fetchImpl(`${baseUrl}${path}`, {
173
- ...init,
174
- signal: controller.signal,
175
- headers: {
176
- Authorization: `Bearer ${apiKey}`,
177
- "cal-api-version": apiVersion,
178
- "Content-Type": "application/json",
179
- ...(init?.headers ?? {}),
180
- },
181
- });
182
-
183
- const body: unknown = await res.json().catch(() => null);
184
- if (res.status === 404) {
185
- return schedulingError("event_not_found", "Cal returned 404.");
186
- }
187
- if (!res.ok) {
188
- // 409 is Cal's "someone took it" for a booking create; everything
189
- // else is an upstream failure this app cannot resolve.
190
- if (res.status === 409) {
191
- return schedulingError(
192
- "slot_unavailable",
193
- "Cal reported the slot is no longer available.",
194
- );
195
- }
196
- return schedulingError(
197
- "provider_error",
198
- `Cal responded ${String(res.status)}.`,
199
- );
200
- }
201
- return { ok: true, value: body };
202
- } catch (err) {
203
- const detail = err instanceof Error ? err.message : "unknown error";
204
- return schedulingError("provider_error", `Cal request failed: ${detail}`);
205
- } finally {
206
- clearTimeout(timer);
207
- }
208
- }
209
-
210
- /** Resolve `"<owner>/<event-slug>"` to the numeric id + derived event meta. */
211
- async function resolveEventType(
212
- calLink: string,
213
- timeZoneOverride?: string,
214
- ): Promise<SchedulingOutcome<{ id: number; meta: SchedulingEventMeta }>> {
215
- const parts = splitCalLink(calLink);
216
- if (!parts) {
217
- return schedulingError(
218
- "invalid_input",
219
- "calLink is not owner/event-slug.",
220
- );
221
- }
222
-
223
- const query = new URLSearchParams({
224
- username: parts.username,
225
- eventTypeSlug: parts.eventTypeSlug,
226
- });
227
- const res = await request(
228
- `/v2/event-types?${query.toString()}`,
229
- EVENT_TYPES_API_VERSION,
230
- );
231
- if (!res.ok) return res;
232
-
233
- const envelope = res.value as { data?: unknown } | null;
234
- const raw = envelope?.data;
235
- // Cal has returned both a bare object and a single-element array here.
236
- const eventType = (Array.isArray(raw) ? raw[0] : raw) as
237
- | Record<string, unknown>
238
- | undefined;
239
- if (!eventType || typeof eventType !== "object") {
240
- return schedulingError("event_not_found", "Cal returned no event type.");
241
- }
242
-
243
- const id = eventType["id"];
244
- const lengthInMinutes =
245
- eventType["lengthInMinutes"] ?? eventType["length"] ?? undefined;
246
- if (typeof id !== "number" || typeof lengthInMinutes !== "number") {
247
- return schedulingError(
248
- "provider_error",
249
- "Cal event type is missing id or duration.",
250
- );
251
- }
252
-
253
- const title = eventType["title"];
254
- const scheduleTimeZone = eventType["scheduleTimeZone"];
255
- const meetingType = readMeetingType(eventType);
256
- // Cal allows an empty description; omit it rather than carry "".
257
- const description = eventType["description"];
258
- const price = readPrice(eventType);
259
- return {
260
- ok: true,
261
- value: {
262
- id,
263
- meta: {
264
- title:
265
- typeof title === "string" && title ? title : parts.eventTypeSlug,
266
- durationMinutes: lengthInMinutes,
267
- ...(typeof description === "string" && description
268
- ? { description }
269
- : {}),
270
- ...(price !== undefined ? { price } : {}),
271
- ...(meetingType !== undefined ? { meetingType } : {}),
272
- timeZone:
273
- timeZoneOverride ??
274
- (typeof scheduleTimeZone === "string" && scheduleTimeZone
275
- ? scheduleTimeZone
276
- : "UTC"),
277
- },
278
- },
279
- };
280
- }
281
-
282
- return {
283
- id: "cal",
284
-
285
- async getEventMeta(
286
- req: GetEventMetaRequest,
287
- ): Promise<SchedulingOutcome<SchedulingEventMeta>> {
288
- // One event-type lookup and nothing else — the availability read's
289
- // /v2/slots call is exactly what this method exists to avoid.
290
- const resolved = await resolveEventType(req.calLink);
291
- if (!resolved.ok) return resolved;
292
- return { ok: true, value: resolved.value.meta };
293
- },
294
-
295
- async listAvailability(
296
- req: ListAvailabilityRequest,
297
- ): Promise<SchedulingOutcome<AvailabilityResult>> {
298
- const resolved = await resolveEventType(req.calLink, req.timeZone);
299
- if (!resolved.ok) return resolved;
300
-
301
- const query = new URLSearchParams({
302
- eventTypeId: String(resolved.value.id),
303
- start: req.from,
304
- end: req.to,
305
- timeZone: resolved.value.meta.timeZone,
306
- });
307
- const res = await request(
308
- `/v2/slots?${query.toString()}`,
309
- SLOTS_API_VERSION,
310
- );
311
- if (!res.ok) return res;
312
-
313
- const envelope = res.value as { data?: unknown } | null;
314
- return {
315
- ok: true,
316
- value: {
317
- event: resolved.value.meta,
318
- slots: flattenSlots(
319
- envelope?.data,
320
- resolved.value.meta.durationMinutes,
321
- ),
322
- },
323
- };
324
- },
325
-
326
- async createBooking(
327
- req: CreateBookingRequest,
328
- ): Promise<SchedulingOutcome<BookingResult>> {
329
- const resolved = await resolveEventType(
330
- req.calLink,
331
- req.attendee.timeZone,
332
- );
333
- if (!resolved.ok) return resolved;
334
-
335
- const res = await request("/v2/bookings", BOOKINGS_API_VERSION, {
336
- method: "POST",
337
- body: JSON.stringify({
338
- start: req.start,
339
- eventTypeId: resolved.value.id,
340
- attendee: {
341
- name: req.attendee.name,
342
- email: req.attendee.email,
343
- timeZone: req.attendee.timeZone ?? resolved.value.meta.timeZone,
344
- language: "en",
345
- },
346
- ...(req.attendee.notes !== undefined
347
- ? { metadata: { notes: req.attendee.notes } }
348
- : {}),
349
- }),
350
- });
351
- if (!res.ok) return res;
352
-
353
- const envelope = res.value as { data?: unknown } | null;
354
- const booking = envelope?.data as Record<string, unknown> | undefined;
355
- if (!booking || typeof booking !== "object") {
356
- return schedulingError("provider_error", "Cal returned no booking.");
357
- }
358
-
359
- const rawId = booking["uid"] ?? booking["id"];
360
- const start = booking["start"];
361
- if (
362
- (typeof rawId !== "string" && typeof rawId !== "number") ||
363
- typeof start !== "string"
364
- ) {
365
- return schedulingError(
366
- "provider_error",
367
- "Cal booking is missing uid or start.",
368
- );
369
- }
370
-
371
- const startMs = Date.parse(start);
372
- const end = booking["end"];
373
- const meetingUrl = booking["meetingUrl"];
374
- return {
375
- ok: true,
376
- value: {
377
- bookingId: String(rawId),
378
- start: new Date(startMs).toISOString(),
379
- end:
380
- typeof end === "string" && !Number.isNaN(Date.parse(end))
381
- ? new Date(Date.parse(end)).toISOString()
382
- : new Date(
383
- startMs + resolved.value.meta.durationMinutes * 60_000,
384
- ).toISOString(),
385
- ...(typeof meetingUrl === "string" && meetingUrl
386
- ? { meetingUrl }
387
- : {}),
388
- },
389
- };
390
- },
391
- };
392
- }
@@ -1,63 +0,0 @@
1
- // oss: false
2
- /**
3
- * Scheduling service entry point (#4416) — provider resolution.
4
- *
5
- * `resolveSchedulingProvider()` returns `null` when no provider is
6
- * configured, and that null is a FIRST-CLASS STATE, not an error: the routes
7
- * turn it into a 503 `scheduling_not_configured`, the MCP twins surface it as
8
- * `not_configured`, and the `booker` section renders its degraded state with
9
- * a direct link out to the provider's own booking page. A deployment that
10
- * never sets `CAL_API_KEY` still ships a page that books.
11
- *
12
- * Config:
13
- * CAL_API_KEY — required to enable scheduling. Server-side only.
14
- * CAL_API_BASE_URL — optional; self-hosted Cal instances.
15
- * SCHEDULING_PROVIDER — optional; defaults to "cal". The config flip that
16
- * #4288's self-hosted engine will answer to.
17
- */
18
-
19
- import { createCalSchedulingProvider } from "./cal-provider.js";
20
- import type { SchedulingProvider } from "./types.js";
21
-
22
- export {
23
- createCalSchedulingProvider,
24
- type CalProviderOptions,
25
- } from "./cal-provider.js";
26
- export {
27
- schedulingError,
28
- type CreateBookingRequest,
29
- type GetEventMetaRequest,
30
- type ListAvailabilityRequest,
31
- type SchedulingError,
32
- type SchedulingOutcome,
33
- type SchedulingProvider,
34
- } from "./types.js";
35
-
36
- /**
37
- * Build the configured provider, or `null` when scheduling is unconfigured.
38
- *
39
- * Read per call rather than cached at module load so a test (and a process
40
- * that loads its env late) sees the current environment — the same posture
41
- * `packages/email`'s Resend client takes with `RESEND_API_KEY`.
42
- */
43
- export function resolveSchedulingProvider(): SchedulingProvider | null {
44
- const providerId = process.env["SCHEDULING_PROVIDER"] ?? "cal";
45
- if (providerId !== "cal") return null;
46
-
47
- const apiKey = process.env["CAL_API_KEY"];
48
- if (!apiKey) return null;
49
-
50
- const baseUrl = process.env["CAL_API_BASE_URL"];
51
- return createCalSchedulingProvider({
52
- apiKey,
53
- ...(baseUrl ? { baseUrl } : {}),
54
- });
55
- }
56
-
57
- /**
58
- * Whether scheduling is configured. Cheap enough to call from a route and
59
- * side-effect free — used for the degraded-state branch.
60
- */
61
- export function isSchedulingConfigured(): boolean {
62
- return resolveSchedulingProvider() !== null;
63
- }
@@ -1,88 +0,0 @@
1
- // oss: false
2
- /**
3
- * The scheduling provider seam (#4416, meta read #4633).
4
- *
5
- * THREE CALLS. That is the whole interface, and keeping it that small is the
6
- * point: the `booker` section is written against `listAvailability` +
7
- * `createBooking`, and a booking tile against `getEventMeta` — the meta-only
8
- * read that exists so rendering an event's facts never costs an availability
9
- * window. The self-hosted scheduling engine (#4288) ships as a second
10
- * implementation of this file's `SchedulingProvider` and nothing in
11
- * `packages/ui`, `apps/mcp`, or the routes changes — same section, same
12
- * content, a config flip.
13
- *
14
- * Nothing Cal-specific appears below. Cal's event-type ids, its per-endpoint
15
- * `cal-api-version` headers, and its slot-map response shape are all confined
16
- * to `cal-provider.ts`; a second provider owes this file only the two methods
17
- * and the shared error vocabulary.
18
- */
19
-
20
- import type {
21
- AvailabilityResult,
22
- BookingResult,
23
- SchedulingAttendee,
24
- SchedulingErrorKind,
25
- SchedulingEventMeta,
26
- } from "@working-theory/validation";
27
-
28
- /** A failure, in the provider-independent vocabulary the routes map to status codes. */
29
- export interface SchedulingError {
30
- kind: SchedulingErrorKind;
31
- /** Operator-facing detail. Safe to log; the routes decide what to expose. */
32
- message: string;
33
- }
34
-
35
- export type SchedulingOutcome<T> =
36
- | { ok: true; value: T }
37
- | { ok: false; error: SchedulingError };
38
-
39
- /** The range + presentation options for an availability read. */
40
- export interface ListAvailabilityRequest {
41
- /** `"<owner>/<event-slug>"`. */
42
- calLink: string;
43
- /** Range start, ISO-8601 instant. */
44
- from: string;
45
- /** Range end, ISO-8601 instant. */
46
- to: string;
47
- /** Present slots in this IANA timezone; omitted → the event's own. */
48
- timeZone?: string;
49
- }
50
-
51
- /**
52
- * The meta-only read (#4633): one event's derived facts, no slots. The route
53
- * fans out over its comma-separated `calLinks`, one of these per link.
54
- */
55
- export interface GetEventMetaRequest {
56
- /** `"<owner>/<event-slug>"`. */
57
- calLink: string;
58
- }
59
-
60
- /** One slot plus who is taking it. */
61
- export interface CreateBookingRequest {
62
- calLink: string;
63
- /** The chosen slot's start, ISO-8601 instant. */
64
- start: string;
65
- attendee: SchedulingAttendee;
66
- }
67
-
68
- export interface SchedulingProvider {
69
- /** Stable identifier for logs and the degraded-state message. */
70
- readonly id: string;
71
- listAvailability(
72
- request: ListAvailabilityRequest,
73
- ): Promise<SchedulingOutcome<AvailabilityResult>>;
74
- createBooking(
75
- request: CreateBookingRequest,
76
- ): Promise<SchedulingOutcome<BookingResult>>;
77
- getEventMeta(
78
- request: GetEventMetaRequest,
79
- ): Promise<SchedulingOutcome<SchedulingEventMeta>>;
80
- }
81
-
82
- /** Convenience constructor so implementations read as one line per failure. */
83
- export function schedulingError(
84
- kind: SchedulingErrorKind,
85
- message: string,
86
- ): { ok: false; error: SchedulingError } {
87
- return { ok: false, error: { kind, message } };
88
- }