create-ailk 0.1.0 → 0.2.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 (620) hide show
  1. package/README.md +124 -3
  2. package/component-catalog.md +456 -0
  3. package/dist/cli.js +92 -16
  4. package/dist/component-catalog.d.ts +11 -0
  5. package/dist/component-catalog.js +39 -0
  6. package/dist/copy.d.ts +7 -0
  7. package/dist/copy.js +24 -0
  8. package/dist/derive-architecture.d.ts +13 -0
  9. package/dist/derive-architecture.js +238 -0
  10. package/dist/index.d.ts +7 -2
  11. package/dist/index.js +19 -2
  12. package/dist/lib/extract-module-bundle.d.ts +16 -0
  13. package/dist/lib/extract-module-bundle.js +184 -0
  14. package/dist/lib/fetch-module.d.ts +55 -0
  15. package/dist/lib/fetch-module.js +125 -0
  16. package/dist/lib/module-license-gate.d.ts +48 -0
  17. package/dist/lib/module-license-gate.js +67 -0
  18. package/dist/lib/module-tier.d.ts +35 -0
  19. package/dist/lib/module-tier.js +37 -0
  20. package/dist/module-architecture.d.ts +175 -0
  21. package/dist/module-architecture.js +663 -0
  22. package/dist/parse-args.d.ts +47 -1
  23. package/dist/parse-args.js +143 -2
  24. package/dist/programmatic.d.ts +160 -3
  25. package/dist/programmatic.js +196 -7
  26. package/dist/surfaces.d.ts +211 -0
  27. package/dist/surfaces.js +532 -0
  28. package/dist/sync-routes.d.ts +92 -0
  29. package/dist/sync-routes.js +350 -0
  30. package/package.json +12 -5
  31. package/templates/.claude/rules/architecture.md +6 -2
  32. package/templates/.claude/skills/README.md +9 -9
  33. package/templates/.claude/skills/add-schema/SKILL.md +14 -7
  34. package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
  35. package/templates/.claude/skills/provision-config/SKILL.md +1 -1
  36. package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
  37. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
  38. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
  39. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
  40. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
  41. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
  42. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
  43. package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
  44. package/templates/.env.example +126 -2
  45. package/templates/CLAUDE.md +11 -5
  46. package/templates/README.md +1 -1
  47. package/templates/apps/api/.env.example +38 -0
  48. package/templates/apps/api/CLAUDE.md +78 -1
  49. package/templates/apps/api/LICENSE +201 -0
  50. package/templates/apps/api/api/index.ts +6 -141
  51. package/templates/apps/api/jest.config.cjs +16 -0
  52. package/templates/apps/api/package.json +22 -6
  53. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
  54. package/templates/apps/api/src/__tests__/server.test.ts +46 -0
  55. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
  56. package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
  57. package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
  58. package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
  59. package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
  60. package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
  61. package/templates/apps/api/src/bin/seed-presets.ts +58 -0
  62. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
  63. package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
  64. package/templates/apps/api/src/cli.ts +5 -11
  65. package/templates/apps/api/src/config/index.ts +35 -0
  66. package/templates/apps/api/src/config/modules.ts +43 -0
  67. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
  68. package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -0
  69. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
  70. package/templates/apps/api/src/lib/site.ts +16 -0
  71. package/templates/apps/api/src/lib/stripe.ts +22 -0
  72. package/templates/apps/api/src/lib/tenant-db.ts +225 -0
  73. package/templates/apps/api/src/lib/ws-token.ts +91 -0
  74. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
  75. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
  77. package/templates/apps/api/src/middleware/auth.ts +43 -8
  78. package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
  79. package/templates/apps/api/src/middleware/tenant.ts +99 -0
  80. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
  81. package/templates/apps/api/src/openapi/spec.ts +312 -3
  82. package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
  83. package/templates/apps/api/src/routes/aeo/index.ts +107 -0
  84. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
  85. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
  86. package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
  87. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
  88. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
  89. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
  90. package/templates/apps/api/src/routes/billing/index.ts +36 -0
  91. package/templates/apps/api/src/routes/billing/portal.ts +182 -0
  92. package/templates/apps/api/src/routes/billing/read.ts +75 -0
  93. package/templates/apps/api/src/routes/billing/usage.ts +153 -0
  94. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
  95. package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
  96. package/templates/apps/api/src/routes/content/create.ts +1 -0
  97. package/templates/apps/api/src/routes/content/delete.ts +1 -0
  98. package/templates/apps/api/src/routes/content/index.ts +42 -6
  99. package/templates/apps/api/src/routes/content/update.ts +1 -0
  100. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
  101. package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
  102. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
  103. package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
  104. package/templates/apps/api/src/routes/flows/README.md +147 -0
  105. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
  106. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
  107. package/templates/apps/api/src/routes/flows/index.ts +202 -0
  108. package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
  109. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
  110. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
  111. package/templates/apps/api/src/routes/leads/index.ts +43 -9
  112. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +558 -0
  113. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
  114. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +869 -0
  115. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
  116. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
  118. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
  119. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
  120. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
  121. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
  122. package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
  123. package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
  124. package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
  125. package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
  126. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
  127. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
  128. package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
  129. package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
  130. package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
  131. package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
  132. package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
  133. package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
  134. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
  135. package/templates/apps/api/src/routes/schedule/index.ts +249 -0
  136. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
  137. package/templates/apps/api/src/routes/slack/actions.ts +177 -0
  138. package/templates/apps/api/src/routes/slack/index.ts +38 -0
  139. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
  140. package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
  141. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
  142. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
  143. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
  144. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
  145. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
  146. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
  147. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
  148. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
  149. package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
  150. package/templates/apps/api/src/routes/webhooks/README.md +35 -12
  151. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
  152. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
  153. package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
  154. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
  155. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
  156. package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
  157. package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
  158. package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
  159. package/templates/apps/api/src/server.ts +307 -7
  160. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
  161. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
  162. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
  163. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
  164. package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
  165. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
  166. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
  167. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
  168. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
  169. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
  170. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
  171. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
  172. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
  173. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
  174. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
  175. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
  176. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
  177. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
  178. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
  179. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
  180. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
  181. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
  182. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
  183. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
  184. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
  185. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
  186. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
  187. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
  188. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
  189. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
  190. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
  191. package/templates/apps/api/src/services/abandon-email.ts +307 -0
  192. package/templates/apps/api/src/services/aeo-score.ts +288 -0
  193. package/templates/apps/api/src/services/answer-distributions.ts +138 -0
  194. package/templates/apps/api/src/services/consent.ts +236 -0
  195. package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
  196. package/templates/apps/api/src/services/entitlements.ts +102 -0
  197. package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
  198. package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
  199. package/templates/apps/api/src/services/flow-engine.ts +1296 -0
  200. package/templates/apps/api/src/services/lead-promotion.ts +176 -0
  201. package/templates/apps/api/src/services/lead-routing.ts +3 -3
  202. package/templates/apps/api/src/services/listing-config.ts +102 -0
  203. package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
  204. package/templates/apps/api/src/services/listing-promotion.ts +342 -0
  205. package/templates/apps/api/src/services/playbook-compile.ts +398 -0
  206. package/templates/apps/api/src/services/playbook-render.ts +263 -0
  207. package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
  208. package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
  209. package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
  210. package/templates/apps/api/src/services/project-listings.ts +1472 -0
  211. package/templates/apps/api/src/services/recommender-capture.ts +835 -0
  212. package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
  213. package/templates/apps/api/src/services/scheduling/index.ts +63 -0
  214. package/templates/apps/api/src/services/scheduling/types.ts +88 -0
  215. package/templates/apps/api/src/services/slack-notify.ts +240 -0
  216. package/templates/apps/api/src/services/tenant-context.ts +451 -0
  217. package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
  218. package/templates/apps/api/src/services/usage-credits.ts +340 -0
  219. package/templates/apps/api/src/services/usage-metering.ts +699 -0
  220. package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
  221. package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
  222. package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
  223. package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
  224. package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
  225. package/templates/apps/api/src/vercel-handler.ts +165 -0
  226. package/templates/apps/api/tsconfig.json +0 -12
  227. package/templates/apps/mcp/CLAUDE.md +19 -9
  228. package/templates/apps/mcp/LICENSE +201 -0
  229. package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
  230. package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
  231. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
  232. package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
  233. package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
  234. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
  235. package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
  236. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
  237. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
  238. package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
  239. package/templates/apps/mcp/package.json +6 -4
  240. package/templates/apps/mcp/src/config/modules.ts +37 -0
  241. package/templates/apps/mcp/src/server.ts +109 -18
  242. package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
  243. package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
  244. package/templates/apps/mcp/src/tools/create_page.ts +1 -0
  245. package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
  246. package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
  247. package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
  248. package/templates/apps/mcp/src/tools/index.ts +25 -0
  249. package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
  250. package/templates/apps/mcp/src/tools/purchase.ts +3 -0
  251. package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
  252. package/templates/apps/mcp/src/tools/update_page.ts +1 -0
  253. package/templates/apps/mcp/tsconfig.json +1 -9
  254. package/templates/apps/web/.env.example +44 -0
  255. package/templates/apps/web/CLAUDE.md +28 -0
  256. package/templates/apps/web/LICENSE +201 -0
  257. package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
  258. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
  259. package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
  260. package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
  261. package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
  262. package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
  263. package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
  264. package/templates/apps/web/__tests__/setup.ts +22 -32
  265. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
  266. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
  267. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
  268. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
  269. package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
  270. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
  271. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
  272. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
  273. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
  274. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
  275. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
  276. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
  277. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
  278. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
  279. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
  280. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
  281. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
  282. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
  283. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
  284. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
  285. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
  286. package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
  287. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
  288. package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
  289. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
  290. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
  291. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  292. package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
  293. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
  294. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  295. package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
  296. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
  297. package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
  298. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
  299. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
  300. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
  301. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
  302. package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
  303. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
  304. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
  305. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
  306. package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
  307. package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
  308. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
  309. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  310. package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
  311. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
  312. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
  313. package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
  314. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
  315. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
  316. package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
  317. package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
  318. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
  319. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
  320. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
  321. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
  322. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
  323. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
  324. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
  325. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
  326. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
  327. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
  328. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
  329. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
  330. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
  331. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
  332. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
  333. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
  334. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  335. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
  336. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  337. package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
  338. package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
  339. package/templates/apps/web/app/[locale]/layout.tsx +127 -16
  340. package/templates/apps/web/app/[locale]/page.tsx +10 -1
  341. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
  342. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  343. package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
  344. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
  345. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  346. package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
  347. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
  348. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
  349. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
  350. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
  351. package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
  352. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
  353. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
  354. package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
  355. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
  356. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
  357. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
  358. package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
  359. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
  360. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  361. package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
  362. package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
  363. package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
  364. package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
  365. package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
  366. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
  367. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
  368. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  369. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
  370. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  371. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
  372. package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
  373. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
  374. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
  375. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
  376. package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
  377. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
  378. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  379. package/templates/apps/web/app/globals.css +35 -0
  380. package/templates/apps/web/app/llms.txt/route.ts +2 -1
  381. package/templates/apps/web/app/og/route.tsx +10 -2
  382. package/templates/apps/web/auth.ts +8 -0
  383. package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
  384. package/templates/apps/web/content/en/blog/README.txt +25 -0
  385. package/templates/apps/web/content/en/docs/index.mdx +28 -0
  386. package/templates/apps/web/jest.config.cjs +90 -27
  387. package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
  388. package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
  389. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
  390. package/templates/apps/web/lib/account-config.ts +13 -0
  391. package/templates/apps/web/lib/server-api.ts +85 -0
  392. package/templates/apps/web/lib/site-brand.tsx +121 -0
  393. package/templates/apps/web/lib/site-theme.ts +74 -0
  394. package/templates/apps/web/messages/en.json +275 -7
  395. package/templates/apps/web/middleware.ts +11 -3
  396. package/templates/apps/web/next.config.mjs +8 -2
  397. package/templates/apps/web/package.json +17 -7
  398. package/templates/apps/web/postcss.config.mjs +23 -0
  399. package/templates/apps/web/public/.well-known/security.txt +3 -3
  400. package/templates/apps/web/src/lib/routes.ts +15 -2
  401. package/templates/apps/web/tsconfig.json +1 -16
  402. package/templates/content/_site.mdx +36 -25
  403. package/templates/content/en/AboutPage/about.mdx +24 -14
  404. package/templates/content/en/ContactPage/contact.mdx +23 -14
  405. package/templates/content/en/FAQPage/faq.mdx +10 -392
  406. package/templates/content/en/HomePage/home.mdx +14 -285
  407. package/templates/database/CHANGELOG.md +443 -0
  408. package/templates/database/LICENSE +201 -0
  409. package/templates/database/README.md +3 -1
  410. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
  411. package/templates/database/__tests__/flows-presets.test.ts +173 -0
  412. package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
  413. package/templates/database/__tests__/seed.test.ts +88 -0
  414. package/templates/database/__tests__/user-cascade.test.ts +19 -4
  415. package/templates/database/auth/schema.prisma +521 -0
  416. package/templates/database/config/schema.prisma +234 -0
  417. package/templates/database/content/schema.prisma +263 -0
  418. package/templates/database/flows/listing-diagnostic.ts +182 -0
  419. package/templates/database/flows/presets.ts +315 -0
  420. package/templates/database/inbox/__tests__/schema.test.ts +4 -0
  421. package/templates/database/inbox/schema.prisma +574 -3
  422. package/templates/database/jest.config.cjs +8 -0
  423. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
  424. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
  425. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
  426. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
  427. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
  428. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
  429. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
  430. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
  431. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
  432. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
  433. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
  434. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
  435. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
  436. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
  437. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
  438. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
  439. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
  440. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
  441. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
  442. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
  443. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
  444. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
  445. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
  446. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
  447. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
  448. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
  449. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
  450. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
  451. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
  452. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
  453. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
  454. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
  455. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
  456. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
  457. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
  458. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
  459. package/templates/database/ops/schema.prisma +27 -0
  460. package/templates/database/package.json +42 -4
  461. package/templates/database/prisma.config.ts +1 -1
  462. package/templates/database/schema.prisma +19 -435
  463. package/templates/database/seed-test.ts +14 -4
  464. package/templates/database/seed.ts +148 -0
  465. package/templates/database/tenancy.ts +45 -0
  466. package/templates/database/tsconfig.flows.json +15 -0
  467. package/templates/database/tsconfig.json +7 -1
  468. package/templates/database/tsconfig.tenancy.json +14 -0
  469. package/templates/package.json +30 -55
  470. package/templates/pnpm-workspace.yaml +0 -3
  471. package/templates/project.yaml +1 -1
  472. package/templates/tsconfig.json +1 -37
  473. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
  474. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
  475. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
  476. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
  477. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
  478. package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
  479. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
  480. package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
  481. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
  482. package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
  483. package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
  484. package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
  485. package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
  486. package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
  487. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
  488. package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
  489. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
  490. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
  491. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
  492. package/templates/apps/web/__tests__/page.test.tsx +0 -230
  493. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
  494. package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
  495. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
  496. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
  497. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
  498. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
  499. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
  500. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
  501. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
  502. package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
  503. package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
  504. package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
  505. package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
  506. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
  507. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
  508. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
  509. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
  510. package/templates/apps/web/content/en/blog/index.mdx +0 -15
  511. package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
  512. package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
  513. package/templates/content/.gitkeep +0 -0
  514. package/templates/content/CLAUDE.md +0 -85
  515. package/templates/content/ar/HomePage/home.mdx +0 -123
  516. package/templates/content/ar/_site.mdx +0 -35
  517. package/templates/content/de/_site.mdx +0 -29
  518. package/templates/content/en/.gitkeep +0 -0
  519. package/templates/content/en/AboutPage/about.schema.json +0 -82
  520. package/templates/content/en/BookCall/book-call.mdx +0 -19
  521. package/templates/content/en/BookCall/book-call.schema.json +0 -121
  522. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
  523. package/templates/content/en/CareersList/careers.mdx +0 -24
  524. package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
  525. package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
  526. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
  527. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
  528. package/templates/content/en/ClientsList/clients.mdx +0 -38
  529. package/templates/content/en/ContactPage/contact.schema.json +0 -100
  530. package/templates/content/en/ContactPoint/primary.mdx +0 -14
  531. package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
  532. package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
  533. package/templates/content/en/CoursesList/courses.mdx +0 -22
  534. package/templates/content/en/DownloadsList/downloads.mdx +0 -22
  535. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
  536. package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
  537. package/templates/content/en/EventsList/events.mdx +0 -26
  538. package/templates/content/en/FAQPage/faq.schema.json +0 -231
  539. package/templates/content/en/GlossaryList/glossary.mdx +0 -19
  540. package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
  541. package/templates/content/en/HomePage/home.schema.json +0 -88
  542. package/templates/content/en/HomePage/launch-service.mdx +0 -475
  543. package/templates/content/en/HomePage/launch-service.schema.json +0 -138
  544. package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
  545. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
  546. package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
  547. package/templates/content/en/Integration/github-actions.mdx +0 -31
  548. package/templates/content/en/Integration/openai-api.mdx +0 -26
  549. package/templates/content/en/Integration/vercel.mdx +0 -32
  550. package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
  551. package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
  552. package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
  553. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
  554. package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
  555. package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
  556. package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
  557. package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
  558. package/templates/content/en/LandingPage/developers.mdx +0 -19
  559. package/templates/content/en/LandingPage/developers.schema.json +0 -109
  560. package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
  561. package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
  562. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
  563. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
  564. package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
  565. package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
  566. package/templates/content/en/LandingPage/platform.mdx +0 -369
  567. package/templates/content/en/LandingPage/platform.schema.json +0 -166
  568. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
  569. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
  570. package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
  571. package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
  572. package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
  573. package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
  574. package/templates/content/en/Organization/site-owner.mdx +0 -16
  575. package/templates/content/en/Partner/prisma.mdx +0 -31
  576. package/templates/content/en/Partner/supabase.mdx +0 -30
  577. package/templates/content/en/Partner/vercel.mdx +0 -28
  578. package/templates/content/en/PartnersList/partners.mdx +0 -22
  579. package/templates/content/en/Person/founder.mdx +0 -18
  580. package/templates/content/en/Pricing/pricing.mdx +0 -538
  581. package/templates/content/en/Pricing/pricing.schema.json +0 -209
  582. package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
  583. package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
  584. package/templates/content/en/Product/ailk.mdx +0 -19
  585. package/templates/content/en/Product/ailk.schema.json +0 -94
  586. package/templates/content/en/Product/api-launch-kit.mdx +0 -26
  587. package/templates/content/en/Product/launch-kit.mdx +0 -28
  588. package/templates/content/en/ProductsList/products.mdx +0 -26
  589. package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
  590. package/templates/content/en/ProjectsList/projects.mdx +0 -25
  591. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
  592. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
  593. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
  594. package/templates/content/en/ResourcesHub/resources.mdx +0 -22
  595. package/templates/content/en/Service/consulting.mdx +0 -22
  596. package/templates/content/en/Service/implementation.mdx +0 -30
  597. package/templates/content/en/Service/launch-service.mdx +0 -232
  598. package/templates/content/en/Service/launch.mdx +0 -228
  599. package/templates/content/en/ServicesList/services.mdx +0 -84
  600. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
  601. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
  602. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
  603. package/templates/content/en/TeamList/team.mdx +0 -63
  604. package/templates/content/en/TeamMember/founder.mdx +0 -72
  605. package/templates/content/en/TermsPage/terms.mdx +0 -19
  606. package/templates/content/en/TermsPage/terms.schema.json +0 -99
  607. package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
  608. package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
  609. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
  610. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
  611. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
  612. package/templates/content/en-XA/AboutPage/about.mdx +0 -8
  613. package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
  614. package/templates/content/en-XA/HomePage/home.mdx +0 -8
  615. package/templates/content/es/_site.mdx +0 -30
  616. package/templates/content/fr/_site.mdx +0 -30
  617. package/templates/content/ja/_site.mdx +0 -30
  618. package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
  619. package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
  620. package/templates/content/pt-BR/HomePage/home.mdx +0 -23
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @file module-exclusion.test.ts
3
+ * @description Module-exclusion regression test (S1, #5088).
4
+ *
5
+ * Proves the mechanical claim card S1 exists to make true: when a paid
6
+ * module is excluded (`AILK_ENABLED_MODULES` omits it), none of the HTTP
7
+ * routes that module contributes register — Fastify's default 404 is the
8
+ * signal, since a request to an unregistered route always 404s regardless
9
+ * of auth/validation state, while a registered-but-rejected request 4xxs
10
+ * with a DIFFERENT code (401/403/400/...). This is the runtime half of "a
11
+ * route and its MCP tool appear or vanish together" (API↔MCP parity) — see
12
+ * the sibling `apps/mcp/__tests__/module-exclusion.test.ts`.
13
+ *
14
+ * Reverting one of `server.ts`'s `isModuleEnabled(...)` conditionals makes
15
+ * the matching case below fail RED — see #5088 S1 verification notes.
16
+ *
17
+ * Mirrors `src/__tests__/server.test.ts`'s external-boundary mocks (CORS,
18
+ * rate-limit, email) — see that file's header for why each is mocked.
19
+ */
20
+
21
+ jest.mock('@fastify/cors', () => {
22
+ return jest.fn().mockResolvedValue(undefined);
23
+ });
24
+
25
+ jest.mock('@fastify/rate-limit', () => {
26
+ return jest.fn().mockResolvedValue(undefined);
27
+ });
28
+
29
+ jest.mock('@working-theory/email', () => ({
30
+ sendLeadNotification: jest.fn().mockResolvedValue({ ok: true }),
31
+ }));
32
+
33
+ import { build } from '../server';
34
+
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- server is typed via Fastify generics; explicit any avoids pulling in the full FastifyInstance type here
36
+ type App = any;
37
+
38
+ async function withEnabledModules<T>(
39
+ enabled: string,
40
+ fn: () => Promise<T>,
41
+ ): Promise<T> {
42
+ const prior = process.env.AILK_ENABLED_MODULES;
43
+ process.env.AILK_ENABLED_MODULES = enabled;
44
+ try {
45
+ return await fn();
46
+ } finally {
47
+ if (prior === undefined) {
48
+ delete process.env.AILK_ENABLED_MODULES;
49
+ } else {
50
+ process.env.AILK_ENABLED_MODULES = prior;
51
+ }
52
+ }
53
+ }
54
+
55
+ // One representative route per module-conditional registration block in
56
+ // server.ts. `always404WhenExcluded` routes are expected to 404 ONLY when
57
+ // their module is absent; every other module stays enabled for each case so
58
+ // a bug that over-excludes (drops an unrelated module's route too) is also
59
+ // caught by the "still registered" assertions below.
60
+ const MODULE_ROUTES: Record<string, { method: 'GET' | 'POST'; url: string }[]> =
61
+ {
62
+ 'multi-tenant': [
63
+ { method: 'GET', url: '/v1/workspaces' },
64
+ { method: 'GET', url: '/v1/billing' },
65
+ { method: 'GET', url: '/v1/waitlist-experiments' },
66
+ { method: 'GET', url: '/v1/project-listings' },
67
+ ],
68
+ scheduling: [{ method: 'GET', url: '/v1/schedule/availability' }],
69
+ 'content-write': [{ method: 'POST', url: '/v1/content/BlogPosting' }],
70
+ };
71
+
72
+ const ALL_MODULES = Object.keys(MODULE_ROUTES);
73
+
74
+ describe('Module exclusion — registration vanishes with the module (S1, #5088)', () => {
75
+ let app: App;
76
+
77
+ afterEach(async () => {
78
+ if (app) {
79
+ await app.close();
80
+ app = undefined;
81
+ }
82
+ delete process.env.AILK_ENABLED_MODULES;
83
+ });
84
+
85
+ it.each(ALL_MODULES)(
86
+ "excluding '%s' 404s every route it owns, while every OTHER module's routes stay registered",
87
+ async (excluded) => {
88
+ const enabled = ALL_MODULES.filter((m) => m !== excluded).join(',');
89
+ app = await withEnabledModules(enabled, () => build());
90
+
91
+ for (const { method, url } of MODULE_ROUTES[excluded]!) {
92
+ const res = await app.inject({ method, url });
93
+ expect(res.statusCode).toBe(404);
94
+ }
95
+
96
+ for (const other of ALL_MODULES) {
97
+ if (other === excluded) continue;
98
+ for (const { method, url } of MODULE_ROUTES[other]!) {
99
+ const res = await app.inject({ method, url });
100
+ expect(res.statusCode).not.toBe(404);
101
+ }
102
+ }
103
+ },
104
+ );
105
+
106
+ it('with every module enabled (the default), every module route registers — no over-exclusion', async () => {
107
+ app = await build();
108
+ for (const routes of Object.values(MODULE_ROUTES)) {
109
+ for (const { method, url } of routes) {
110
+ const res = await app.inject({ method, url });
111
+ expect(res.statusCode).not.toBe(404);
112
+ }
113
+ }
114
+ // Free/never-gate surfaces are unaffected by any module exclusion.
115
+ const health = await app.inject({ method: 'GET', url: '/health' });
116
+ expect(health.statusCode).toBe(200);
117
+ const contentTypes = await app.inject({
118
+ method: 'GET',
119
+ url: '/v1/content/types',
120
+ });
121
+ expect(contentTypes.statusCode).not.toBe(404);
122
+ });
123
+
124
+ it('excluding every paid module at once still serves the free surface', async () => {
125
+ app = await withEnabledModules('', () => build());
126
+ const health = await app.inject({ method: 'GET', url: '/health' });
127
+ expect(health.statusCode).toBe(200);
128
+ const contentTypes = await app.inject({
129
+ method: 'GET',
130
+ url: '/v1/content/types',
131
+ });
132
+ expect(contentTypes.statusCode).not.toBe(404);
133
+
134
+ for (const routes of Object.values(MODULE_ROUTES)) {
135
+ for (const { method, url } of routes) {
136
+ const res = await app.inject({ method, url });
137
+ expect(res.statusCode).toBe(404);
138
+ }
139
+ }
140
+ });
141
+ });
@@ -204,4 +204,50 @@ describe('Parser scope — webhooks raw-body parser does not leak', () => {
204
204
  await server.close();
205
205
  }
206
206
  });
207
+
208
+ describe('per-scope rate-limit defaults (#5111, DEF-004)', () => {
209
+ // Real end-to-end behavior against the real in-house rate-limit
210
+ // middleware (NOT mocked — this repo's convention is to mock only real
211
+ // external boundaries, never an internal module, see the file header).
212
+ // aeoScope's `/v1/aeo/score/example` needs no DB/network (a bundled
213
+ // example, `loadExampleScore()`); flowsScope's `/v1/flows/:slug` with a
214
+ // schema-invalid slug returns 400 before any DB access — both let the
215
+ // onRequest rate-limit hook be the only thing under test, regardless of
216
+ // the eventual handler status.
217
+ it('caps aeoScope at the strict pre-#4730 default (10/min) — the 11th request 429s', async () => {
218
+ app = await build();
219
+ const ip = '198.51.100.11';
220
+
221
+ for (let i = 0; i < 10; i++) {
222
+ const res = await app.inject({
223
+ method: 'GET',
224
+ url: '/v1/aeo/score/example',
225
+ headers: { 'x-forwarded-for': ip },
226
+ });
227
+ expect(res.statusCode).not.toBe(429);
228
+ }
229
+
230
+ const eleventh = await app.inject({
231
+ method: 'GET',
232
+ url: '/v1/aeo/score/example',
233
+ headers: { 'x-forwarded-for': ip },
234
+ });
235
+ expect(eleventh.statusCode).toBe(429);
236
+ });
237
+
238
+ it('leaves flowsScope above the strict default — 11 requests never 429', async () => {
239
+ app = await build();
240
+ const ip = '198.51.100.12';
241
+
242
+ for (let i = 0; i < 11; i++) {
243
+ const res = await app.inject({
244
+ method: 'GET',
245
+ url: '/v1/flows/not-a-real-slug!!!',
246
+ headers: { 'x-forwarded-for': ip },
247
+ });
248
+ // Schema-invalid slug → 400, well before any rate limit or DB call.
249
+ expect(res.statusCode).toBe(400);
250
+ }
251
+ });
252
+ });
207
253
  });
@@ -0,0 +1,211 @@
1
+ /**
2
+ * @file vercel-handler.test.ts
3
+ * @description Regression coverage for the two independent defects in the
4
+ * Vercel serverless adapter (#5168):
5
+ *
6
+ * - request side: the adapter forwards the client's original content-length
7
+ * verbatim, but the body it hands to inject() is a JS object that
8
+ * light-my-request re-serializes itself. light-my-request only computes a
9
+ * fresh content-length when the header is ABSENT (it does
10
+ * `req.headers['content-length'] = req.headers['content-length'] || ...`)
11
+ * — a stale header therefore survives and Fastify's content-length check
12
+ * rejects any body whose re-serialized length differs from what the client
13
+ * declared for the original bytes (FST_ERR_CTP_INVALID_CONTENT_LENGTH).
14
+ * - response side: sending response.body (light-my-request's UTF-8-decoded
15
+ * string) instead of response.rawPayload corrupts any non-UTF-8 response
16
+ * by substituting U+FFFD for every invalid byte.
17
+ *
18
+ * The two request-side tests below drive a REAL minimal Fastify instance
19
+ * through the handler — the defect lives inside Fastify's own
20
+ * content-length check, so a mocked inject() cannot fail the way production
21
+ * fails. The response-side test mocks inject() because the assertion is
22
+ * about what the adapter forwards to res.send, not about Fastify's own
23
+ * behavior.
24
+ *
25
+ * Each test resets the module registry and re-mocks '../server' itself
26
+ * (rather than sharing one top-level jest.mock) because the handler
27
+ * memoizes its built server instance at module scope (getServer()) —
28
+ * without a fresh module per test, the first test's server would be reused
29
+ * by every later test regardless of what that test wants build() to return.
30
+ */
31
+
32
+ import type { VercelRequest, VercelResponse } from '@vercel/node';
33
+ import Fastify from 'fastify';
34
+
35
+ // ── Typed request/response stubs ────────────────────────────────────────────
36
+ //
37
+ // Minimal stubs that satisfy only the VercelRequest/VercelResponse surface
38
+ // the handler actually reads/calls, each cast to the real type ONCE at a
39
+ // dedicated boundary function — avoids the `as unknown as <T>` double
40
+ // assertion pattern the rest of the call sites would otherwise repeat
41
+ // (mirrors apps/api/src/lib/__tests__/require-paid-tier.test.ts's
42
+ // `asReply`/`StubReply` pattern).
43
+
44
+ interface MockVercelRequest {
45
+ method: string;
46
+ url: string;
47
+ headers: Record<string, string>;
48
+ body: unknown;
49
+ query: Record<string, string>;
50
+ }
51
+
52
+ function asRequest(stub: MockVercelRequest): VercelRequest {
53
+ // @ts-expect-error: stub satisfies the subset of VercelRequest the handler
54
+ // reads (method/url/headers/body/query); a full VercelRequest (extends
55
+ // http.IncomingMessage) cannot be constructed without a live connection.
56
+ return stub;
57
+ }
58
+
59
+ interface MockRes {
60
+ headersSent: boolean;
61
+ _status?: number;
62
+ _headers: Record<string, string>;
63
+ _sent?: unknown;
64
+ status(code: number): MockRes;
65
+ setHeader(key: string, value: string): MockRes;
66
+ getHeader(key: string): string | undefined;
67
+ send(body: unknown): MockRes;
68
+ json(body: unknown): MockRes;
69
+ }
70
+
71
+ function asResponse(stub: MockRes): VercelResponse {
72
+ // @ts-expect-error: stub satisfies the subset of VercelResponse the handler
73
+ // calls (status/setHeader/getHeader/send/json); a full VercelResponse
74
+ // (extends http.ServerResponse) cannot be constructed without a live
75
+ // connection.
76
+ return stub;
77
+ }
78
+
79
+ function makeRes(): MockRes {
80
+ const headers: Record<string, string> = {};
81
+ const res: MockRes = {
82
+ headersSent: false,
83
+ _headers: headers,
84
+ status(code: number) {
85
+ res._status = code;
86
+ return res;
87
+ },
88
+ setHeader(key: string, value: string) {
89
+ headers[key] = value;
90
+ return res;
91
+ },
92
+ getHeader(key: string) {
93
+ return headers[key];
94
+ },
95
+ send(body: unknown) {
96
+ res._sent = body;
97
+ return res;
98
+ },
99
+ json(body: unknown) {
100
+ res._sent = body;
101
+ return res;
102
+ },
103
+ };
104
+ return res;
105
+ }
106
+
107
+ // Re-required per test (after jest.resetModules()) rather than statically
108
+ // imported — a static import can't reach a freshly reset module.
109
+ function loadHandler(): (req: VercelRequest, res: VercelResponse) => Promise<void> {
110
+ return require('../vercel-handler').default;
111
+ }
112
+
113
+ describe('vercel-handler — request side: stale content-length (#5168)', () => {
114
+ let app: ReturnType<typeof Fastify>;
115
+
116
+ beforeEach(async () => {
117
+ jest.resetModules();
118
+ app = Fastify();
119
+ app.post('/echo', async (req) => req.body);
120
+ await app.ready();
121
+ });
122
+
123
+ afterEach(async () => {
124
+ await app.close();
125
+ });
126
+
127
+ it('accepts a JSON body serialized with indent=2', async () => {
128
+ jest.doMock('../server', () => ({ build: jest.fn().mockResolvedValue(app) }));
129
+ const handler = loadHandler();
130
+
131
+ const body = { hello: 'world', nested: { a: 1 } };
132
+ const pretty = JSON.stringify(body, null, 2);
133
+ const req: MockVercelRequest = {
134
+ method: 'POST',
135
+ url: '/echo',
136
+ headers: {
137
+ 'content-type': 'application/json',
138
+ 'content-length': String(Buffer.byteLength(pretty)),
139
+ },
140
+ body,
141
+ query: {},
142
+ };
143
+ const res = makeRes();
144
+
145
+ await handler(asRequest(req), asResponse(res));
146
+
147
+ expect(res._status).toBe(200);
148
+ expect(JSON.parse(res._sent as string)).toEqual(body);
149
+ });
150
+
151
+ it('accepts a compact JSON body plus a single trailing newline', async () => {
152
+ jest.doMock('../server', () => ({ build: jest.fn().mockResolvedValue(app) }));
153
+ const handler = loadHandler();
154
+
155
+ const body = { hello: 'world' };
156
+ const compactPlusNewline = JSON.stringify(body) + '\n';
157
+ const req: MockVercelRequest = {
158
+ method: 'POST',
159
+ url: '/echo',
160
+ headers: {
161
+ 'content-type': 'application/json',
162
+ 'content-length': String(Buffer.byteLength(compactPlusNewline)),
163
+ },
164
+ body,
165
+ query: {},
166
+ };
167
+ const res = makeRes();
168
+
169
+ await handler(asRequest(req), asResponse(res));
170
+
171
+ expect(res._status).toBe(200);
172
+ expect(JSON.parse(res._sent as string)).toEqual(body);
173
+ });
174
+ });
175
+
176
+ describe('vercel-handler — response side: binary round-trip (#5168)', () => {
177
+ beforeEach(() => {
178
+ jest.resetModules();
179
+ });
180
+
181
+ it('forwards rawPayload byte-identically for a non-UTF-8 response', async () => {
182
+ // '%PDF-' header followed by bytes that are invalid UTF-8 on their own
183
+ // (0x80, 0xFF are continuation/invalid bytes outside any valid sequence).
184
+ const rawPayload = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2d, 0x80, 0xff, 0xfe]);
185
+ // What light-my-request's UTF-8-decoded `.body` would mangle those bytes
186
+ // into — every invalid byte replaced with U+FFFD.
187
+ const mangledBody = rawPayload.toString('utf8');
188
+ const inject = jest.fn().mockResolvedValue({
189
+ statusCode: 200,
190
+ headers: { 'content-type': 'application/pdf' },
191
+ body: mangledBody,
192
+ rawPayload,
193
+ });
194
+ jest.doMock('../server', () => ({ build: jest.fn().mockResolvedValue({ inject }) }));
195
+ const handler = loadHandler();
196
+
197
+ const req: MockVercelRequest = {
198
+ method: 'GET',
199
+ url: '/pdf',
200
+ headers: {},
201
+ body: undefined,
202
+ query: {},
203
+ };
204
+ const res = makeRes();
205
+
206
+ await handler(asRequest(req), asResponse(res));
207
+
208
+ expect(Buffer.isBuffer(res._sent)).toBe(true);
209
+ expect(Buffer.compare(res._sent as Buffer, rawPayload)).toBe(0);
210
+ });
211
+ });
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /* istanbul ignore file -- CLI bootstrap; the underlying runAbandonEmailSweep
3
+ logic is unit-tested in services/__tests__/abandon-email.test.ts. Mirrors
4
+ cli.ts's coverage-ignore rationale — real entry-point semantics run the
5
+ compiled dist directly, on the operator's own cron/scheduler cadence (D1:
6
+ no queue/timer infrastructure in this repo — an external scheduler runs
7
+ this command on whatever cadence the deployment chooses). */
8
+ /**
9
+ * Abandon-email sweep CLI entry (#3962 D1).
10
+ *
11
+ * Dev: `pnpm --filter @working-theory/api abandon:sweep` (tsx, this file).
12
+ * Production: `node dist/bin/abandon-sweep.js` (the compiled entry, run by
13
+ * any external scheduler — Vercel Cron, GitHub Actions cron, plain crontab).
14
+ * The sweep is stateless and idempotent (D5), so overlap or a missed tick
15
+ * is harmless — the delay window is data, not a timer.
16
+ */
17
+ import { runAbandonEmailSweep } from "../services/abandon-email.js";
18
+
19
+ async function main(): Promise<void> {
20
+ const result = await runAbandonEmailSweep();
21
+
22
+ if (!result.ok) {
23
+ console.error(
24
+ `[abandon-sweep] ${result.error.kind}: ${result.error.message}`,
25
+ );
26
+ process.exitCode = 1;
27
+ return;
28
+ }
29
+
30
+ console.log(`[abandon-sweep] ${JSON.stringify(result.summary)}`);
31
+ }
32
+
33
+ main().catch((err: unknown) => {
34
+ // Belt-and-suspenders (code-review finding on #3962's acceptance PR):
35
+ // runAbandonEmailSweep already catches internally and returns a
36
+ // structured `unexpected_error` result, so this path is unreachable in
37
+ // practice — it exists only to prevent a bare unhandled-rejection crash
38
+ // if `main` itself ever throws before/around that call.
39
+ const message = err instanceof Error ? err.message : "Unknown error";
40
+ console.error(`[abandon-sweep] unexpected_error: ${message}`);
41
+ process.exitCode = 1;
42
+ });
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /* istanbul ignore file -- CLI bootstrap; mirrors abandon-sweep.ts's coverage-ignore
3
+ rationale — the underlying list/resend logic is unit-tested at the service
4
+ layer (services/__tests__/deliverable-resend.test.ts), through the same
5
+ mocked-prisma double the fulfillment service tests already use. This file
6
+ is a thin entry: parse args, dispatch to list or resend, print, set exit
7
+ code. */
8
+ /**
9
+ * Deliverable-grant resend CLI (#3998 D2/D4).
10
+ *
11
+ * Dev — list mode (read-only, default):
12
+ * `pnpm --filter @working-theory/api deliverable:resend -- --site <siteId>
13
+ * [--since <iso>] [--limit <n>]`
14
+ * Dev — resend mode (one grant):
15
+ * `pnpm --filter @working-theory/api deliverable:resend -- --grant <grantId>`
16
+ * Production: `node dist/bin/deliverable-resend.js <same flags>`.
17
+ *
18
+ * No HTTP resend route exists (spec §2.2/D2) — this CLI is the sole recovery
19
+ * path for a grant whose post-mint send never completed, run by the operator
20
+ * on their own machine/CI. List mode is the read-only default and can never
21
+ * send anything. `--grant` is always a deliberate, named, single-grant act
22
+ * (D4) — there is no bulk/`--all` mode; the operator loops over the list
23
+ * output if several need resending.
24
+ */
25
+ import {
26
+ listSendlessGrants,
27
+ resendDeliverableGrant,
28
+ } from "../services/deliverable-fulfillment.js";
29
+
30
+ type ParsedArgs = {
31
+ site?: string | undefined;
32
+ since?: string | undefined;
33
+ limit?: string | undefined;
34
+ grant?: string | undefined;
35
+ };
36
+
37
+ /** Minimal `--flag value` parser — no external dependency for a 4-flag CLI. */
38
+ function parseArgs(argv: string[]): ParsedArgs {
39
+ const out: ParsedArgs = {};
40
+ for (let i = 0; i < argv.length; i += 1) {
41
+ const flag = argv[i];
42
+ const value = argv[i + 1];
43
+ if (flag === "--site") out.site = value;
44
+ else if (flag === "--since") out.since = value;
45
+ else if (flag === "--limit") out.limit = value;
46
+ else if (flag === "--grant") out.grant = value;
47
+ else continue;
48
+ i += 1;
49
+ }
50
+ return out;
51
+ }
52
+
53
+ async function runResend(grantId: string): Promise<void> {
54
+ const result = await resendDeliverableGrant(grantId);
55
+
56
+ if (!result.ok) {
57
+ console.error(
58
+ `[deliverable-resend] ${result.error.kind}: ${result.error.message}`,
59
+ );
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+
64
+ console.log(
65
+ `[deliverable-resend] grant="${grantId}" outcome="${result.outcome}"`,
66
+ );
67
+ if (result.outcome !== "sent") {
68
+ process.exitCode = 1;
69
+ }
70
+ }
71
+
72
+ async function runList(args: ParsedArgs): Promise<void> {
73
+ if (!args.site) {
74
+ console.error(
75
+ "[deliverable-resend] usage: --site <siteId> [--since <iso>] [--limit <n>]",
76
+ );
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+
81
+ let since: Date | undefined;
82
+ if (args.since !== undefined) {
83
+ since = new Date(args.since);
84
+ if (Number.isNaN(since.getTime())) {
85
+ console.error(
86
+ `[deliverable-resend] invalid --since value: "${args.since}"`,
87
+ );
88
+ process.exitCode = 1;
89
+ return;
90
+ }
91
+ } else {
92
+ // D4: every historical (pre-migration) grant reads sendStatus IS NULL,
93
+ // so an unfiltered first run on an established site lists every grant
94
+ // ever minted — most of which were, in fact, delivered via the prior
95
+ // log-only path. The caveat is printed, not silently assumed away.
96
+ console.log(
97
+ "[deliverable-resend] no --since given — listing EVERY sendless grant " +
98
+ "for this site, including pre-migration rows that predate this " +
99
+ "column and may already have been delivered.",
100
+ );
101
+ }
102
+
103
+ let limit: number | undefined;
104
+ if (args.limit !== undefined) {
105
+ limit = Number.parseInt(args.limit, 10);
106
+ if (!Number.isInteger(limit) || limit <= 0) {
107
+ console.error(
108
+ `[deliverable-resend] invalid --limit value: "${args.limit}"`,
109
+ );
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ }
114
+
115
+ const rows = await listSendlessGrants({
116
+ siteId: args.site,
117
+ ...(since !== undefined && { since }),
118
+ ...(limit !== undefined && { limit }),
119
+ });
120
+
121
+ for (const row of rows) {
122
+ console.log(JSON.stringify(row));
123
+ }
124
+ console.log(`[deliverable-resend] ${rows.length} sendless grant(s)`);
125
+ }
126
+
127
+ async function main(): Promise<void> {
128
+ const args = parseArgs(process.argv.slice(2));
129
+
130
+ if (args.grant) {
131
+ await runResend(args.grant);
132
+ } else {
133
+ await runList(args);
134
+ }
135
+ }
136
+
137
+ main().catch((err: unknown) => {
138
+ // Belt-and-suspenders (mirrors abandon-sweep.ts) — an unexpected throw
139
+ // (a bad DB connection, etc.) still exits non-zero with a structured
140
+ // message rather than an unhandled-rejection crash.
141
+ const message = err instanceof Error ? err.message : "Unknown error";
142
+ console.error(`[deliverable-resend] unexpected_error: ${message}`);
143
+ process.exitCode = 1;
144
+ });