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.
- package/README.md +124 -3
- package/component-catalog.md +456 -0
- package/dist/cli.js +92 -16
- package/dist/component-catalog.d.ts +11 -0
- package/dist/component-catalog.js +39 -0
- package/dist/copy.d.ts +7 -0
- package/dist/copy.js +24 -0
- package/dist/derive-architecture.d.ts +13 -0
- package/dist/derive-architecture.js +238 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +19 -2
- package/dist/lib/extract-module-bundle.d.ts +16 -0
- package/dist/lib/extract-module-bundle.js +184 -0
- package/dist/lib/fetch-module.d.ts +55 -0
- package/dist/lib/fetch-module.js +125 -0
- package/dist/lib/module-license-gate.d.ts +48 -0
- package/dist/lib/module-license-gate.js +67 -0
- package/dist/lib/module-tier.d.ts +35 -0
- package/dist/lib/module-tier.js +37 -0
- package/dist/module-architecture.d.ts +175 -0
- package/dist/module-architecture.js +663 -0
- package/dist/parse-args.d.ts +47 -1
- package/dist/parse-args.js +143 -2
- package/dist/programmatic.d.ts +160 -3
- package/dist/programmatic.js +196 -7
- package/dist/surfaces.d.ts +211 -0
- package/dist/surfaces.js +532 -0
- package/dist/sync-routes.d.ts +92 -0
- package/dist/sync-routes.js +350 -0
- package/package.json +12 -5
- package/templates/.claude/rules/architecture.md +6 -2
- package/templates/.claude/skills/README.md +9 -9
- package/templates/.claude/skills/add-schema/SKILL.md +14 -7
- package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
- package/templates/.claude/skills/provision-config/SKILL.md +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
- package/templates/.env.example +126 -2
- package/templates/CLAUDE.md +11 -5
- package/templates/README.md +1 -1
- package/templates/apps/api/.env.example +38 -0
- package/templates/apps/api/CLAUDE.md +78 -1
- package/templates/apps/api/LICENSE +201 -0
- package/templates/apps/api/api/index.ts +6 -141
- package/templates/apps/api/jest.config.cjs +16 -0
- package/templates/apps/api/package.json +22 -6
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
- package/templates/apps/api/src/__tests__/server.test.ts +46 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
- package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
- package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
- package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
- package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
- package/templates/apps/api/src/bin/seed-presets.ts +58 -0
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
- package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
- package/templates/apps/api/src/cli.ts +5 -11
- package/templates/apps/api/src/config/index.ts +35 -0
- package/templates/apps/api/src/config/modules.ts +43 -0
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -0
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
- package/templates/apps/api/src/lib/site.ts +16 -0
- package/templates/apps/api/src/lib/stripe.ts +22 -0
- package/templates/apps/api/src/lib/tenant-db.ts +225 -0
- package/templates/apps/api/src/lib/ws-token.ts +91 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
- package/templates/apps/api/src/middleware/auth.ts +43 -8
- package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
- package/templates/apps/api/src/middleware/tenant.ts +99 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
- package/templates/apps/api/src/openapi/spec.ts +312 -3
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
- package/templates/apps/api/src/routes/aeo/index.ts +107 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
- package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
- package/templates/apps/api/src/routes/billing/index.ts +36 -0
- package/templates/apps/api/src/routes/billing/portal.ts +182 -0
- package/templates/apps/api/src/routes/billing/read.ts +75 -0
- package/templates/apps/api/src/routes/billing/usage.ts +153 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
- package/templates/apps/api/src/routes/content/create.ts +1 -0
- package/templates/apps/api/src/routes/content/delete.ts +1 -0
- package/templates/apps/api/src/routes/content/index.ts +42 -6
- package/templates/apps/api/src/routes/content/update.ts +1 -0
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
- package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
- package/templates/apps/api/src/routes/flows/README.md +147 -0
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
- package/templates/apps/api/src/routes/flows/index.ts +202 -0
- package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
- package/templates/apps/api/src/routes/leads/index.ts +43 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +558 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +869 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
- package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
- package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
- package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
- package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
- package/templates/apps/api/src/routes/schedule/index.ts +249 -0
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
- package/templates/apps/api/src/routes/slack/actions.ts +177 -0
- package/templates/apps/api/src/routes/slack/index.ts +38 -0
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
- package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
- package/templates/apps/api/src/routes/webhooks/README.md +35 -12
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
- package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
- package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
- package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
- package/templates/apps/api/src/server.ts +307 -7
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
- package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
- package/templates/apps/api/src/services/abandon-email.ts +307 -0
- package/templates/apps/api/src/services/aeo-score.ts +288 -0
- package/templates/apps/api/src/services/answer-distributions.ts +138 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
- package/templates/apps/api/src/services/entitlements.ts +102 -0
- package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
- package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
- package/templates/apps/api/src/services/flow-engine.ts +1296 -0
- package/templates/apps/api/src/services/lead-promotion.ts +176 -0
- package/templates/apps/api/src/services/lead-routing.ts +3 -3
- package/templates/apps/api/src/services/listing-config.ts +102 -0
- package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
- package/templates/apps/api/src/services/listing-promotion.ts +342 -0
- package/templates/apps/api/src/services/playbook-compile.ts +398 -0
- package/templates/apps/api/src/services/playbook-render.ts +263 -0
- package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
- package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
- package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
- package/templates/apps/api/src/services/project-listings.ts +1472 -0
- package/templates/apps/api/src/services/recommender-capture.ts +835 -0
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
- package/templates/apps/api/src/services/scheduling/index.ts +63 -0
- package/templates/apps/api/src/services/scheduling/types.ts +88 -0
- package/templates/apps/api/src/services/slack-notify.ts +240 -0
- package/templates/apps/api/src/services/tenant-context.ts +451 -0
- package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
- package/templates/apps/api/src/services/usage-credits.ts +340 -0
- package/templates/apps/api/src/services/usage-metering.ts +699 -0
- package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
- package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
- package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
- package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
- package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
- package/templates/apps/api/src/vercel-handler.ts +165 -0
- package/templates/apps/api/tsconfig.json +0 -12
- package/templates/apps/mcp/CLAUDE.md +19 -9
- package/templates/apps/mcp/LICENSE +201 -0
- package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
- package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
- package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
- package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
- package/templates/apps/mcp/package.json +6 -4
- package/templates/apps/mcp/src/config/modules.ts +37 -0
- package/templates/apps/mcp/src/server.ts +109 -18
- package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
- package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
- package/templates/apps/mcp/src/tools/create_page.ts +1 -0
- package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
- package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
- package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
- package/templates/apps/mcp/src/tools/index.ts +25 -0
- package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
- package/templates/apps/mcp/src/tools/purchase.ts +3 -0
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
- package/templates/apps/mcp/src/tools/update_page.ts +1 -0
- package/templates/apps/mcp/tsconfig.json +1 -9
- package/templates/apps/web/.env.example +44 -0
- package/templates/apps/web/CLAUDE.md +28 -0
- package/templates/apps/web/LICENSE +201 -0
- package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
- package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
- package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
- package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
- package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
- package/templates/apps/web/__tests__/setup.ts +22 -32
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
- package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/layout.tsx +127 -16
- package/templates/apps/web/app/[locale]/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
- package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
- package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
- package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
- package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/globals.css +35 -0
- package/templates/apps/web/app/llms.txt/route.ts +2 -1
- package/templates/apps/web/app/og/route.tsx +10 -2
- package/templates/apps/web/auth.ts +8 -0
- package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
- package/templates/apps/web/content/en/blog/README.txt +25 -0
- package/templates/apps/web/content/en/docs/index.mdx +28 -0
- package/templates/apps/web/jest.config.cjs +90 -27
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
- package/templates/apps/web/lib/account-config.ts +13 -0
- package/templates/apps/web/lib/server-api.ts +85 -0
- package/templates/apps/web/lib/site-brand.tsx +121 -0
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/messages/en.json +275 -7
- package/templates/apps/web/middleware.ts +11 -3
- package/templates/apps/web/next.config.mjs +8 -2
- package/templates/apps/web/package.json +17 -7
- package/templates/apps/web/postcss.config.mjs +23 -0
- package/templates/apps/web/public/.well-known/security.txt +3 -3
- package/templates/apps/web/src/lib/routes.ts +15 -2
- package/templates/apps/web/tsconfig.json +1 -16
- package/templates/content/_site.mdx +36 -25
- package/templates/content/en/AboutPage/about.mdx +24 -14
- package/templates/content/en/ContactPage/contact.mdx +23 -14
- package/templates/content/en/FAQPage/faq.mdx +10 -392
- package/templates/content/en/HomePage/home.mdx +14 -285
- package/templates/database/CHANGELOG.md +443 -0
- package/templates/database/LICENSE +201 -0
- package/templates/database/README.md +3 -1
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
- package/templates/database/__tests__/flows-presets.test.ts +173 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
- package/templates/database/__tests__/seed.test.ts +88 -0
- package/templates/database/__tests__/user-cascade.test.ts +19 -4
- package/templates/database/auth/schema.prisma +521 -0
- package/templates/database/config/schema.prisma +234 -0
- package/templates/database/content/schema.prisma +263 -0
- package/templates/database/flows/listing-diagnostic.ts +182 -0
- package/templates/database/flows/presets.ts +315 -0
- package/templates/database/inbox/__tests__/schema.test.ts +4 -0
- package/templates/database/inbox/schema.prisma +574 -3
- package/templates/database/jest.config.cjs +8 -0
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/ops/schema.prisma +27 -0
- package/templates/database/package.json +42 -4
- package/templates/database/prisma.config.ts +1 -1
- package/templates/database/schema.prisma +19 -435
- package/templates/database/seed-test.ts +14 -4
- package/templates/database/seed.ts +148 -0
- package/templates/database/tenancy.ts +45 -0
- package/templates/database/tsconfig.flows.json +15 -0
- package/templates/database/tsconfig.json +7 -1
- package/templates/database/tsconfig.tenancy.json +14 -0
- package/templates/package.json +30 -55
- package/templates/pnpm-workspace.yaml +0 -3
- package/templates/project.yaml +1 -1
- package/templates/tsconfig.json +1 -37
- package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
- package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
- package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
- package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
- package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
- package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
- package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
- package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
- package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
- package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
- package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
- package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
- package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
- package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
- package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
- package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
- package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
- package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
- package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
- package/templates/apps/web/__tests__/page.test.tsx +0 -230
- package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
- package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
- package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
- package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
- package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
- package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
- package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
- package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
- package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
- package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
- package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
- package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
- package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
- package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
- package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
- package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
- package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
- package/templates/apps/web/content/en/blog/index.mdx +0 -15
- package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
- package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
- package/templates/content/.gitkeep +0 -0
- package/templates/content/CLAUDE.md +0 -85
- package/templates/content/ar/HomePage/home.mdx +0 -123
- package/templates/content/ar/_site.mdx +0 -35
- package/templates/content/de/_site.mdx +0 -29
- package/templates/content/en/.gitkeep +0 -0
- package/templates/content/en/AboutPage/about.schema.json +0 -82
- package/templates/content/en/BookCall/book-call.mdx +0 -19
- package/templates/content/en/BookCall/book-call.schema.json +0 -121
- package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
- package/templates/content/en/CareersList/careers.mdx +0 -24
- package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
- package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
- package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
- package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
- package/templates/content/en/ClientsList/clients.mdx +0 -38
- package/templates/content/en/ContactPage/contact.schema.json +0 -100
- package/templates/content/en/ContactPoint/primary.mdx +0 -14
- package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
- package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
- package/templates/content/en/CoursesList/courses.mdx +0 -22
- package/templates/content/en/DownloadsList/downloads.mdx +0 -22
- package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
- package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
- package/templates/content/en/EventsList/events.mdx +0 -26
- package/templates/content/en/FAQPage/faq.schema.json +0 -231
- package/templates/content/en/GlossaryList/glossary.mdx +0 -19
- package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
- package/templates/content/en/HomePage/home.schema.json +0 -88
- package/templates/content/en/HomePage/launch-service.mdx +0 -475
- package/templates/content/en/HomePage/launch-service.schema.json +0 -138
- package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
- package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
- package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
- package/templates/content/en/Integration/github-actions.mdx +0 -31
- package/templates/content/en/Integration/openai-api.mdx +0 -26
- package/templates/content/en/Integration/vercel.mdx +0 -32
- package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
- package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
- package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
- package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
- package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
- package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
- package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
- package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
- package/templates/content/en/LandingPage/developers.mdx +0 -19
- package/templates/content/en/LandingPage/developers.schema.json +0 -109
- package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
- package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
- package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
- package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
- package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
- package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
- package/templates/content/en/LandingPage/platform.mdx +0 -369
- package/templates/content/en/LandingPage/platform.schema.json +0 -166
- package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
- package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
- package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
- package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
- package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
- package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
- package/templates/content/en/Organization/site-owner.mdx +0 -16
- package/templates/content/en/Partner/prisma.mdx +0 -31
- package/templates/content/en/Partner/supabase.mdx +0 -30
- package/templates/content/en/Partner/vercel.mdx +0 -28
- package/templates/content/en/PartnersList/partners.mdx +0 -22
- package/templates/content/en/Person/founder.mdx +0 -18
- package/templates/content/en/Pricing/pricing.mdx +0 -538
- package/templates/content/en/Pricing/pricing.schema.json +0 -209
- package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
- package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
- package/templates/content/en/Product/ailk.mdx +0 -19
- package/templates/content/en/Product/ailk.schema.json +0 -94
- package/templates/content/en/Product/api-launch-kit.mdx +0 -26
- package/templates/content/en/Product/launch-kit.mdx +0 -28
- package/templates/content/en/ProductsList/products.mdx +0 -26
- package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
- package/templates/content/en/ProjectsList/projects.mdx +0 -25
- package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
- package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
- package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
- package/templates/content/en/ResourcesHub/resources.mdx +0 -22
- package/templates/content/en/Service/consulting.mdx +0 -22
- package/templates/content/en/Service/implementation.mdx +0 -30
- package/templates/content/en/Service/launch-service.mdx +0 -232
- package/templates/content/en/Service/launch.mdx +0 -228
- package/templates/content/en/ServicesList/services.mdx +0 -84
- package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
- package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
- package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
- package/templates/content/en/TeamList/team.mdx +0 -63
- package/templates/content/en/TeamMember/founder.mdx +0 -72
- package/templates/content/en/TermsPage/terms.mdx +0 -19
- package/templates/content/en/TermsPage/terms.schema.json +0 -99
- package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
- package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
- package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
- package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
- package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
- package/templates/content/en-XA/AboutPage/about.mdx +0 -8
- package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
- package/templates/content/en-XA/HomePage/home.mdx +0 -8
- package/templates/content/es/_site.mdx +0 -30
- package/templates/content/fr/_site.mdx +0 -30
- package/templates/content/ja/_site.mdx +0 -30
- package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
- package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
- package/templates/content/pt-BR/HomePage/home.mdx +0 -23
|
@@ -0,0 +1,392 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
}
|