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,242 @@
|
|
|
1
|
+
// oss: false
|
|
2
|
+
/**
|
|
3
|
+
* Tests for the list_availability + get_event_meta + create_booking MCP
|
|
4
|
+
* tools (#4416, #4633).
|
|
5
|
+
*
|
|
6
|
+
* The claim these tools make is PARITY OF CODE, not just of endpoint: an
|
|
7
|
+
* agent that hits a taken slot must read `slot_unavailable`, the same word
|
|
8
|
+
* the route emitted and the same word the booker section branches on. Most of
|
|
9
|
+
* what follows pins that mapping.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ailkCreateBooking } from "../src/tools/create_booking.js";
|
|
13
|
+
import { ailkGetEventMeta } from "../src/tools/get_event_meta.js";
|
|
14
|
+
import {
|
|
15
|
+
ailkListAvailability,
|
|
16
|
+
schedulingKindFromStatus,
|
|
17
|
+
} from "../src/tools/list_availability.js";
|
|
18
|
+
|
|
19
|
+
jest.mock("@working-theory/api-client", () => ({
|
|
20
|
+
apiClient: {
|
|
21
|
+
schedule: {
|
|
22
|
+
availability: jest.fn(),
|
|
23
|
+
eventMeta: jest.fn(),
|
|
24
|
+
book: jest.fn(),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const { apiClient } = require("@working-theory/api-client") as {
|
|
30
|
+
apiClient: {
|
|
31
|
+
schedule: { availability: jest.Mock; eventMeta: jest.Mock; book: jest.Mock };
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const VALID_RANGE = {
|
|
36
|
+
calLink: "sam/fit-check",
|
|
37
|
+
from: "2026-08-26T00:00:00.000Z",
|
|
38
|
+
to: "2026-09-02T00:00:00.000Z",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const AVAILABILITY = {
|
|
42
|
+
event: {
|
|
43
|
+
title: "15-Minute Call with Sam",
|
|
44
|
+
durationMinutes: 15,
|
|
45
|
+
meetingType: "Google Meet",
|
|
46
|
+
timeZone: "America/Denver",
|
|
47
|
+
},
|
|
48
|
+
slots: [
|
|
49
|
+
{ start: "2026-08-27T16:00:00.000Z", end: "2026-08-27T16:15:00.000Z" },
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
jest.clearAllMocks();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("schedulingKindFromStatus", () => {
|
|
58
|
+
it.each([
|
|
59
|
+
[400, "invalid_input"],
|
|
60
|
+
[404, "event_not_found"],
|
|
61
|
+
[409, "slot_unavailable"],
|
|
62
|
+
[503, "not_configured"],
|
|
63
|
+
[502, "provider_error"],
|
|
64
|
+
[500, "provider_error"],
|
|
65
|
+
])("maps %i to %s", (status, kind) => {
|
|
66
|
+
expect(schedulingKindFromStatus(status as number)).toBe(kind);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("ailkListAvailability", () => {
|
|
71
|
+
it("rejects a malformed calLink without calling api-client", async () => {
|
|
72
|
+
const result = await ailkListAvailability({
|
|
73
|
+
...VALID_RANGE,
|
|
74
|
+
calLink: "https://cal.com/sam/fit-check",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(result.ok).toBe(false);
|
|
78
|
+
if (!result.ok) expect(result.error.kind).toBe("invalid_input");
|
|
79
|
+
expect(apiClient.schedule.availability).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("rejects a range wider than the 60-day cap without calling api-client", async () => {
|
|
83
|
+
const result = await ailkListAvailability({
|
|
84
|
+
...VALID_RANGE,
|
|
85
|
+
to: "2027-08-26T00:00:00.000Z",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result.ok).toBe(false);
|
|
89
|
+
expect(apiClient.schedule.availability).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("forwards the validated range and returns the derived event meta", async () => {
|
|
93
|
+
apiClient.schedule.availability.mockResolvedValueOnce({
|
|
94
|
+
ok: true,
|
|
95
|
+
body: AVAILABILITY,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await ailkListAvailability(VALID_RANGE);
|
|
99
|
+
|
|
100
|
+
expect(apiClient.schedule.availability).toHaveBeenCalledWith(VALID_RANGE);
|
|
101
|
+
expect(result.ok).toBe(true);
|
|
102
|
+
if (result.ok) {
|
|
103
|
+
expect(result.data.event.durationMinutes).toBe(15);
|
|
104
|
+
expect(result.data.slots).toHaveLength(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("surfaces an unconfigured deploy as not_configured, not a bare status", async () => {
|
|
109
|
+
apiClient.schedule.availability.mockResolvedValueOnce({
|
|
110
|
+
ok: false,
|
|
111
|
+
status: 503,
|
|
112
|
+
body: null,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = await ailkListAvailability(VALID_RANGE);
|
|
116
|
+
|
|
117
|
+
expect(result.ok).toBe(false);
|
|
118
|
+
if (!result.ok) expect(result.error.kind).toBe("not_configured");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("ailkGetEventMeta", () => {
|
|
123
|
+
const EVENTS = {
|
|
124
|
+
events: [
|
|
125
|
+
{
|
|
126
|
+
calLink: "sam/fit-check",
|
|
127
|
+
event: {
|
|
128
|
+
title: "15-Minute Call with Sam",
|
|
129
|
+
durationMinutes: 15,
|
|
130
|
+
price: { amountMinor: 0, currency: "usd" },
|
|
131
|
+
timeZone: "America/Denver",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
calLink: "sam/power-hour",
|
|
136
|
+
event: {
|
|
137
|
+
title: "Power Hour",
|
|
138
|
+
durationMinutes: 60,
|
|
139
|
+
description: "Sixty focused minutes on your positioning.",
|
|
140
|
+
price: { amountMinor: 25000, currency: "usd" },
|
|
141
|
+
timeZone: "America/Denver",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
it("rejects a malformed link in the list without calling api-client", async () => {
|
|
148
|
+
const result = await ailkGetEventMeta({
|
|
149
|
+
calLinks: "sam/fit-check,https://cal.com/sam/power-hour",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result.ok).toBe(false);
|
|
153
|
+
if (!result.ok) expect(result.error.kind).toBe("invalid_input");
|
|
154
|
+
expect(apiClient.schedule.eventMeta).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("forwards the links and returns meta only — a free and a priced event", async () => {
|
|
158
|
+
apiClient.schedule.eventMeta.mockResolvedValueOnce({
|
|
159
|
+
ok: true,
|
|
160
|
+
body: EVENTS,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const result = await ailkGetEventMeta({
|
|
164
|
+
calLinks: "sam/fit-check,sam/power-hour",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(apiClient.schedule.eventMeta).toHaveBeenCalledWith({
|
|
168
|
+
calLinks: "sam/fit-check,sam/power-hour",
|
|
169
|
+
});
|
|
170
|
+
expect(result.ok).toBe(true);
|
|
171
|
+
if (result.ok) {
|
|
172
|
+
expect(result.data.events).toHaveLength(2);
|
|
173
|
+
expect(result.data.events[0]?.event.price?.amountMinor).toBe(0);
|
|
174
|
+
expect(result.data.events[1]?.event.price?.amountMinor).toBe(25000);
|
|
175
|
+
expect(result.data.events[1]?.event.description).toBe(
|
|
176
|
+
"Sixty focused minutes on your positioning.",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("surfaces an unconfigured deploy as not_configured, not a bare status", async () => {
|
|
182
|
+
apiClient.schedule.eventMeta.mockResolvedValueOnce({
|
|
183
|
+
ok: false,
|
|
184
|
+
status: 503,
|
|
185
|
+
body: null,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const result = await ailkGetEventMeta({ calLinks: "sam/fit-check" });
|
|
189
|
+
|
|
190
|
+
expect(result.ok).toBe(false);
|
|
191
|
+
if (!result.ok) expect(result.error.kind).toBe("not_configured");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("ailkCreateBooking", () => {
|
|
196
|
+
const VALID_BOOKING = {
|
|
197
|
+
calLink: "sam/fit-check",
|
|
198
|
+
start: "2026-08-27T16:00:00.000Z",
|
|
199
|
+
attendee: { name: "Sam Rivera", email: "sam@example.com" },
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
it("rejects a malformed attendee email without calling api-client", async () => {
|
|
203
|
+
const result = await ailkCreateBooking({
|
|
204
|
+
...VALID_BOOKING,
|
|
205
|
+
attendee: { name: "Sam Rivera", email: "sam@" },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(result.ok).toBe(false);
|
|
209
|
+
if (!result.ok) expect(result.error.kind).toBe("invalid_input");
|
|
210
|
+
expect(apiClient.schedule.book).not.toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("forwards the booking and returns the provider's identifier", async () => {
|
|
214
|
+
apiClient.schedule.book.mockResolvedValueOnce({
|
|
215
|
+
ok: true,
|
|
216
|
+
body: {
|
|
217
|
+
bookingId: "bkg_9",
|
|
218
|
+
start: "2026-08-27T16:00:00.000Z",
|
|
219
|
+
end: "2026-08-27T16:15:00.000Z",
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const result = await ailkCreateBooking(VALID_BOOKING);
|
|
224
|
+
|
|
225
|
+
expect(apiClient.schedule.book).toHaveBeenCalledWith(VALID_BOOKING);
|
|
226
|
+
expect(result.ok).toBe(true);
|
|
227
|
+
if (result.ok) expect(result.data.bookingId).toBe("bkg_9");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("surfaces a lost race as slot_unavailable so the caller can retry another slot", async () => {
|
|
231
|
+
apiClient.schedule.book.mockResolvedValueOnce({
|
|
232
|
+
ok: false,
|
|
233
|
+
status: 409,
|
|
234
|
+
body: null,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const result = await ailkCreateBooking(VALID_BOOKING);
|
|
238
|
+
|
|
239
|
+
expect(result.ok).toBe(false);
|
|
240
|
+
if (!result.ok) expect(result.error.kind).toBe("slot_unavailable");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -55,7 +55,7 @@ describe("ailkCaptureLead — happy path", () => {
|
|
|
55
55
|
it("forwards canonical body to api-client and returns ok:true with id and persisted", async () => {
|
|
56
56
|
apiClient.leads.submit.mockResolvedValueOnce({
|
|
57
57
|
ok: true,
|
|
58
|
-
body: { id: "
|
|
58
|
+
body: { id: "lead_00000000-0000-7000-8000-0000000000d1", persisted: "db" },
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const result = await ailkCaptureLead(VALID_INPUT);
|
|
@@ -68,7 +68,7 @@ describe("ailkCaptureLead — happy path", () => {
|
|
|
68
68
|
});
|
|
69
69
|
expect(result.ok).toBe(true);
|
|
70
70
|
if (result.ok) {
|
|
71
|
-
expect(result.id).toBe("
|
|
71
|
+
expect(result.id).toBe("lead_00000000-0000-7000-8000-0000000000d1");
|
|
72
72
|
expect(result.persisted).toBe("db");
|
|
73
73
|
}
|
|
74
74
|
});
|
|
@@ -135,7 +135,7 @@ describe("ailkCaptureLead — siteId default", () => {
|
|
|
135
135
|
delete process.env.AILK_SITE_ID;
|
|
136
136
|
apiClient.leads.submit.mockResolvedValueOnce({
|
|
137
137
|
ok: true,
|
|
138
|
-
body: { id: "
|
|
138
|
+
body: { id: "lead_00000000-0000-7000-8000-0000000000d2", persisted: "db" },
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
await ailkCaptureLead(VALID_INPUT);
|
|
@@ -154,7 +154,7 @@ describe("ailkCaptureLead — siteId override", () => {
|
|
|
154
154
|
process.env.AILK_SITE_ID = "site-abc";
|
|
155
155
|
apiClient.leads.submit.mockResolvedValueOnce({
|
|
156
156
|
ok: true,
|
|
157
|
-
body: { id: "
|
|
157
|
+
body: { id: "lead_00000000-0000-7000-8000-0000000000d3", persisted: "db" },
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
await ailkCaptureLead(VALID_INPUT);
|
|
@@ -52,7 +52,7 @@ describe("ailkSubscribeNewsletter — happy path", () => {
|
|
|
52
52
|
it("forwards canonical body to api-client and returns ok:true with id and persisted", async () => {
|
|
53
53
|
apiClient.leads.subscribe.mockResolvedValueOnce({
|
|
54
54
|
ok: true,
|
|
55
|
-
body: { id: "
|
|
55
|
+
body: { id: "subscr_00000000-0000-7000-8000-0000000000d1", persisted: "db" },
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
const result = await ailkSubscribeNewsletter(VALID_INPUT);
|
|
@@ -64,7 +64,7 @@ describe("ailkSubscribeNewsletter — happy path", () => {
|
|
|
64
64
|
});
|
|
65
65
|
expect(result.ok).toBe(true);
|
|
66
66
|
if (result.ok) {
|
|
67
|
-
expect(result.id).toBe("
|
|
67
|
+
expect(result.id).toBe("subscr_00000000-0000-7000-8000-0000000000d1");
|
|
68
68
|
expect(result.persisted).toBe("db");
|
|
69
69
|
}
|
|
70
70
|
});
|
|
@@ -131,7 +131,7 @@ describe("ailkSubscribeNewsletter — siteId default", () => {
|
|
|
131
131
|
delete process.env.AILK_SITE_ID;
|
|
132
132
|
apiClient.leads.subscribe.mockResolvedValueOnce({
|
|
133
133
|
ok: true,
|
|
134
|
-
body: { id: "
|
|
134
|
+
body: { id: "subscr_00000000-0000-7000-8000-0000000000d2", persisted: "db" },
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
await ailkSubscribeNewsletter(VALID_INPUT);
|
|
@@ -150,7 +150,7 @@ describe("ailkSubscribeNewsletter — siteId override", () => {
|
|
|
150
150
|
process.env.AILK_SITE_ID = "site-abc";
|
|
151
151
|
apiClient.leads.subscribe.mockResolvedValueOnce({
|
|
152
152
|
ok: true,
|
|
153
|
-
body: { id: "
|
|
153
|
+
body: { id: "subscr_00000000-0000-7000-8000-0000000000d3", persisted: "db" },
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
await ailkSubscribeNewsletter(VALID_INPUT);
|
|
@@ -54,6 +54,22 @@ describe("PurchaseInputSchema", () => {
|
|
|
54
54
|
expect(result.success).toBe(true);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
it("accepts an optional allowPromotionCodes boolean", () => {
|
|
58
|
+
const result = PurchaseInputSchema.safeParse({
|
|
59
|
+
priceId: "price_test_123",
|
|
60
|
+
allowPromotionCodes: true,
|
|
61
|
+
});
|
|
62
|
+
expect(result.success).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("rejects a non-boolean allowPromotionCodes", () => {
|
|
66
|
+
const result = PurchaseInputSchema.safeParse({
|
|
67
|
+
priceId: "price_test_123",
|
|
68
|
+
allowPromotionCodes: "yes",
|
|
69
|
+
});
|
|
70
|
+
expect(result.success).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
57
73
|
it("rejects input missing priceId", () => {
|
|
58
74
|
const result = PurchaseInputSchema.safeParse({ quantity: 1 });
|
|
59
75
|
expect(result.success).toBe(false);
|
|
@@ -132,6 +148,37 @@ describe("ailkPurchase — valid input", () => {
|
|
|
132
148
|
expect(body.quantity).toBe(1);
|
|
133
149
|
});
|
|
134
150
|
|
|
151
|
+
it("forwards allowPromotionCodes when supplied, omits it otherwise", async () => {
|
|
152
|
+
const okResponse = () => ({
|
|
153
|
+
ok: true,
|
|
154
|
+
status: 201,
|
|
155
|
+
json: () =>
|
|
156
|
+
Promise.resolve({
|
|
157
|
+
sessionId: "cs_test_promo",
|
|
158
|
+
url: "https://checkout.stripe.com/pay/cs_test_promo",
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
mockFetch.mockResolvedValueOnce(okResponse());
|
|
162
|
+
|
|
163
|
+
await ailkPurchase({
|
|
164
|
+
priceId: "price_test_123",
|
|
165
|
+
allowPromotionCodes: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const withFlag = JSON.parse(
|
|
169
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
170
|
+
) as Record<string, unknown>;
|
|
171
|
+
expect(withFlag.allowPromotionCodes).toBe(true);
|
|
172
|
+
|
|
173
|
+
mockFetch.mockResolvedValueOnce(okResponse());
|
|
174
|
+
await ailkPurchase({ priceId: "price_test_123" });
|
|
175
|
+
|
|
176
|
+
const withoutFlag = JSON.parse(
|
|
177
|
+
(mockFetch.mock.calls[1] as [string, RequestInit])[1].body as string,
|
|
178
|
+
) as Record<string, unknown>;
|
|
179
|
+
expect(withoutFlag).not.toHaveProperty("allowPromotionCodes");
|
|
180
|
+
});
|
|
181
|
+
|
|
135
182
|
it("returns { url } on success", async () => {
|
|
136
183
|
mockFetch.mockResolvedValueOnce({
|
|
137
184
|
ok: true,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"scripts": {
|
|
9
|
+
"lint": "tsx ../../scripts/lint-package.ts",
|
|
9
10
|
"build": "tsc -b",
|
|
10
11
|
"type-check": "tsc --noEmit",
|
|
11
12
|
"dev": "node --experimental-vm-modules dist/index.js",
|
|
@@ -14,6 +15,9 @@
|
|
|
14
15
|
"test:coverage": "jest --coverage"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
18
|
+
"@fastify/cors": "^11.2.0",
|
|
19
|
+
"@fastify/rate-limit": "^10.3.0",
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
17
21
|
"@working-theory/aeo": "workspace:*",
|
|
18
22
|
"@working-theory/api-client": "workspace:*",
|
|
19
23
|
"@working-theory/content-adapters": "workspace:*",
|
|
@@ -21,10 +25,8 @@
|
|
|
21
25
|
"@working-theory/metadata": "workspace:*",
|
|
22
26
|
"@working-theory/schema": "workspace:*",
|
|
23
27
|
"@working-theory/validation": "workspace:*",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
27
|
-
"fastify": "^5.7.2",
|
|
28
|
+
"fastify": "^5.12.1",
|
|
29
|
+
"yaml": "^2.9.0",
|
|
28
30
|
"zod": "^4.2.1"
|
|
29
31
|
},
|
|
30
32
|
"devDependencies": {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paid-module presence — the registration-grain signal used by `server.ts`
|
|
3
|
+
* to decide whether a paid module's tools are wired into this build
|
|
4
|
+
* (S1, #5088). Mirrors `apps/api/src/config/modules.ts` — apps/* may not
|
|
5
|
+
* import apps/*, so this tiny reader is duplicated rather than shared.
|
|
6
|
+
*
|
|
7
|
+
* `AILK_ENABLED_MODULES` is a comma-separated allowlist (e.g.
|
|
8
|
+
* `"multi-tenant,scheduling"`). Unset means "every module on" — today's
|
|
9
|
+
* behavior, unchanged for every existing deployment; an explicit empty
|
|
10
|
+
* string (`""`) means "no paid modules" (the OSS-strip build target).
|
|
11
|
+
*
|
|
12
|
+
* Recognized module keys: `docs`, `multi-tenant`, `scheduling` (the
|
|
13
|
+
* `ModuleName` vocabulary `packages/create-ailk/src/module-architecture.ts`
|
|
14
|
+
* already models via `MODULE_SLICES`) plus `content-write` — the Pro
|
|
15
|
+
* content-write surface (`create_page`/`update_page`/`delete_page`).
|
|
16
|
+
* `content-write` is intentionally NOT a `MODULE_SLICES` entry: it is not
|
|
17
|
+
* (yet) a create-ailk `--modules` selector, so it stays a local flag here
|
|
18
|
+
* rather than widening the create-ailk module vocabulary.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function enabledModules(): Set<string> | undefined {
|
|
22
|
+
const raw = process.env.AILK_ENABLED_MODULES;
|
|
23
|
+
if (raw === undefined) return undefined; // undefined sentinel = "all enabled"
|
|
24
|
+
return new Set(
|
|
25
|
+
raw
|
|
26
|
+
.split(",")
|
|
27
|
+
.map((m) => m.trim())
|
|
28
|
+
.filter(Boolean),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Whether the named paid module's tools should register on this build/boot. */
|
|
33
|
+
export function isModuleEnabled(moduleName: string): boolean {
|
|
34
|
+
const enabled = enabledModules();
|
|
35
|
+
if (enabled === undefined) return true;
|
|
36
|
+
return enabled.has(moduleName);
|
|
37
|
+
}
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
* each catalog entry to its handler.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { getMcpToolRegistrations, OSS_DISABLED_TOOLS } from "@working-theory/metadata";
|
|
16
15
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
|
+
import { getMcpToolRegistrations, OSS_DISABLED_TOOLS } from "@working-theory/metadata";
|
|
17
17
|
|
|
18
|
+
import { isModuleEnabled } from "./config/modules.js";
|
|
18
19
|
import { ailkAudit } from "./tools/audit.js";
|
|
19
20
|
import { ailkCaptureLead } from "./tools/capture_lead.js";
|
|
20
|
-
import { ailkCreatePage } from "./tools/create_page.js";
|
|
21
|
-
import { ailkDeletePage } from "./tools/delete_page.js";
|
|
22
21
|
import { ailkGetBusinessHours } from "./tools/get_business_hours.js";
|
|
23
22
|
import { ailkGetFaq } from "./tools/get_faq.js";
|
|
23
|
+
import { ailkGetFlow } from "./tools/get_flow.js";
|
|
24
24
|
import { ailkGetLocations } from "./tools/get_locations.js";
|
|
25
25
|
import { ailkGetOfferings } from "./tools/get_offerings.js";
|
|
26
26
|
import { ailkGetPricing } from "./tools/get_pricing.js";
|
|
@@ -31,8 +31,8 @@ import { ailkReadContent } from "./tools/read_content.js";
|
|
|
31
31
|
import { ailkRequestCallback } from "./tools/request_callback.js";
|
|
32
32
|
import { ailkScaffold } from "./tools/scaffold.js";
|
|
33
33
|
import { ailkSearchContent } from "./tools/search_content.js";
|
|
34
|
+
import { ailkSubmitFlowStep } from "./tools/submit_flow_step.js";
|
|
34
35
|
import { ailkSubscribeNewsletter } from "./tools/subscribe_newsletter.js";
|
|
35
|
-
import { ailkUpdatePage } from "./tools/update_page.js";
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Tool-name → handler map. Each handler accepts the parsed input the SDK
|
|
@@ -65,16 +65,87 @@ const HANDLERS: Record<string, (input: unknown) => Promise<unknown>> = {
|
|
|
65
65
|
subscribe_newsletter: (input) => ailkSubscribeNewsletter(input as never),
|
|
66
66
|
request_callback: (input) => ailkRequestCallback(input as never),
|
|
67
67
|
scaffold: (input) => ailkScaffold(input as never),
|
|
68
|
+
get_flow: (input) => ailkGetFlow(input as never),
|
|
69
|
+
submit_flow_step: (input) => ailkSubmitFlowStep(input as never),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type ToolHandler = (input: unknown) => Promise<unknown>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Module-conditional tool handlers — the FILE-grain half of S1's registration
|
|
76
|
+
* gate (#5182).
|
|
77
|
+
*
|
|
78
|
+
* `shouldRegister` already keeps a paid module's tools out of the registered
|
|
79
|
+
* catalog when the module is absent, but that is a RUNTIME filter: a static
|
|
80
|
+
* `import` of the handler still pins the file into the TYPE graph, so an OSS
|
|
81
|
+
* strip that deletes it reds `tsc`. A literal `await import()` does not help —
|
|
82
|
+
* `import()` defers evaluation, not resolution, so `tsc` resolves the specifier
|
|
83
|
+
* either way and reports TS2307 once the file is gone.
|
|
84
|
+
*
|
|
85
|
+
* The specifier therefore comes from this table rather than being written
|
|
86
|
+
* inline, which is what makes it non-literal at the call site and so
|
|
87
|
+
* unresolvable by `tsc`. The cast in {@link resolveHandler} is the load-bearing
|
|
88
|
+
* line: `import(nonLiteral)` yields `any`, and the cast is the only thing
|
|
89
|
+
* restoring a type at the module boundary.
|
|
90
|
+
*
|
|
91
|
+
* Every entry here is a slice file the strip deletes; every entry's catalog
|
|
92
|
+
* row carries the matching `module` tag, so `shouldRegister` and this table
|
|
93
|
+
* appear or vanish together.
|
|
94
|
+
*/
|
|
95
|
+
const MODULE_TOOL_ENTRIES: Record<
|
|
96
|
+
string,
|
|
97
|
+
{ readonly specifier: string; readonly exportName: string }
|
|
98
|
+
> = {
|
|
99
|
+
// #4416 — the scheduling twins of GET /v1/schedule/availability and
|
|
100
|
+
// POST /v1/schedule/book. Anonymous, identity-blind, credential-free: the
|
|
101
|
+
// scheduling credential lives in apps/api, which these reach via api-client.
|
|
102
|
+
list_availability: {
|
|
103
|
+
specifier: "./tools/list_availability.js",
|
|
104
|
+
exportName: "ailkListAvailability",
|
|
105
|
+
},
|
|
106
|
+
get_event_meta: {
|
|
107
|
+
specifier: "./tools/get_event_meta.js",
|
|
108
|
+
exportName: "ailkGetEventMeta",
|
|
109
|
+
},
|
|
110
|
+
create_booking: {
|
|
111
|
+
specifier: "./tools/create_booking.js",
|
|
112
|
+
exportName: "ailkCreateBooking",
|
|
113
|
+
},
|
|
68
114
|
// D14a Stage 1 Unit C (#55): Paid-tier content write surface. Handlers
|
|
69
115
|
// self-gate via requirePaidTier and return canonical error bodies (no throw)
|
|
70
116
|
// for license / validation / conflict / not-found cases — see
|
|
71
117
|
// apps/mcp/src/lib/errors.ts. Catalog entries carry tier: "pro" so agents
|
|
72
118
|
// see the gating before invoking via list_tools.
|
|
73
|
-
create_page:
|
|
74
|
-
update_page:
|
|
75
|
-
delete_page:
|
|
119
|
+
create_page: { specifier: "./tools/create_page.js", exportName: "ailkCreatePage" },
|
|
120
|
+
update_page: { specifier: "./tools/update_page.js", exportName: "ailkUpdatePage" },
|
|
121
|
+
delete_page: { specifier: "./tools/delete_page.js", exportName: "ailkDeletePage" },
|
|
76
122
|
};
|
|
77
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Resolves a tool name to its handler: the statically-imported map first, then
|
|
126
|
+
* the module-conditional table. Called from inside the already-`async`
|
|
127
|
+
* `server.tool` callback so {@link createMcpServer} stays synchronous — seven
|
|
128
|
+
* files call it that way, one of them production (`src/index.ts`), and making
|
|
129
|
+
* it async would rewrite all seven for no gain.
|
|
130
|
+
*/
|
|
131
|
+
async function resolveHandler(name: string): Promise<ToolHandler> {
|
|
132
|
+
const direct = HANDLERS[name];
|
|
133
|
+
if (direct) return direct;
|
|
134
|
+
|
|
135
|
+
const entry = MODULE_TOOL_ENTRIES[name];
|
|
136
|
+
if (!entry) {
|
|
137
|
+
throw new Error(`MCP catalog drift: no handler for tool '${name}'`);
|
|
138
|
+
}
|
|
139
|
+
const mod = (await import(entry.specifier)) as Record<string, ToolHandler | undefined>;
|
|
140
|
+
const fn = mod[entry.exportName];
|
|
141
|
+
if (!fn) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`MCP slice drift: '${entry.specifier}' does not export '${entry.exportName}'`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return fn;
|
|
147
|
+
}
|
|
148
|
+
|
|
78
149
|
/**
|
|
79
150
|
* Tool descriptor returned by {@link listRegisteredTools}. A minimal shape
|
|
80
151
|
* sufficient for callers that need to assert which tools are registered
|
|
@@ -86,10 +157,25 @@ export interface RegisteredToolDescriptor {
|
|
|
86
157
|
description: string;
|
|
87
158
|
}
|
|
88
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Whether a catalog registration entry should register on this build/boot —
|
|
162
|
+
* the OSS-disabled filter (static per-name set) AND the module-presence
|
|
163
|
+
* filter (S1, #5088): a tool tagged with `module` (e.g. `create_booking`
|
|
164
|
+
* carrying `module: "scheduling"`) registers only when
|
|
165
|
+
* {@link isModuleEnabled} says its module is present, the same way its
|
|
166
|
+
* `apps/api` HTTP route is module-conditional in `server.ts` there — so a
|
|
167
|
+
* tool and its route appear or vanish together, holding API↔MCP parity.
|
|
168
|
+
*/
|
|
169
|
+
function shouldRegister(reg: { name: string; module?: string }): boolean {
|
|
170
|
+
if (OSS_DISABLED_TOOLS.has(reg.name)) return false;
|
|
171
|
+
if (reg.module && !isModuleEnabled(reg.module)) return false;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
89
175
|
/**
|
|
90
176
|
* Returns the list of tools that {@link createMcpServer} will register, after
|
|
91
|
-
* the OSS catalog filter ({@link OSS_DISABLED_TOOLS})
|
|
92
|
-
* server instance is constructed.
|
|
177
|
+
* the OSS catalog filter ({@link OSS_DISABLED_TOOLS}) and the module-presence
|
|
178
|
+
* filter are applied. Pure — no server instance is constructed.
|
|
93
179
|
*
|
|
94
180
|
* Consumed by `apps/mcp/__tests__/purchase-disabled.test.ts` to assert that
|
|
95
181
|
* disabled tools are absent from the registered catalog. If a future change
|
|
@@ -98,20 +184,22 @@ export interface RegisteredToolDescriptor {
|
|
|
98
184
|
*/
|
|
99
185
|
export function listRegisteredTools(): RegisteredToolDescriptor[] {
|
|
100
186
|
return getMcpToolRegistrations()
|
|
101
|
-
.filter(
|
|
187
|
+
.filter(shouldRegister)
|
|
102
188
|
.map((reg) => ({ name: reg.name, description: reg.description }));
|
|
103
189
|
}
|
|
104
190
|
|
|
105
191
|
/**
|
|
106
192
|
* Creates a configured McpServer with all AILK tools registered, less any
|
|
107
|
-
* tool name in {@link OSS_DISABLED_TOOLS}
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
193
|
+
* tool name in {@link OSS_DISABLED_TOOLS} and less any tool whose `module`
|
|
194
|
+
* is not enabled on this build (S1, #5088; see {@link shouldRegister}). The
|
|
195
|
+
* disabled filter is applied defensively here even though the upstream
|
|
196
|
+
* `getMcpToolRegistrations()` already excludes them — a belt-and-braces
|
|
197
|
+
* guard so a future regression in the metadata package can't accidentally
|
|
198
|
+
* re-register a disabled tool.
|
|
111
199
|
*
|
|
112
200
|
* Re-enable criteria for each disabled tool live at
|
|
113
201
|
* `docs/internal/roadmaps/phase-3-readiness.md`. S15a (issue #610) is the
|
|
114
|
-
* source of
|
|
202
|
+
* source of the disabled-tool filter.
|
|
115
203
|
*/
|
|
116
204
|
export function createMcpServer(): McpServer {
|
|
117
205
|
const server = new McpServer({
|
|
@@ -120,14 +208,17 @@ export function createMcpServer(): McpServer {
|
|
|
120
208
|
});
|
|
121
209
|
|
|
122
210
|
for (const reg of getMcpToolRegistrations()) {
|
|
123
|
-
if (
|
|
211
|
+
if (!shouldRegister(reg)) {
|
|
124
212
|
continue;
|
|
125
213
|
}
|
|
126
|
-
|
|
127
|
-
|
|
214
|
+
// Boot-time drift detection is preserved: a registered name must be
|
|
215
|
+
// resolvable from ONE of the two tables, checked here rather than at first
|
|
216
|
+
// invocation, so a catalog/handler mismatch still fails at startup.
|
|
217
|
+
if (!HANDLERS[reg.name] && !MODULE_TOOL_ENTRIES[reg.name]) {
|
|
128
218
|
throw new Error(`MCP catalog drift: no handler for tool '${reg.name}'`);
|
|
129
219
|
}
|
|
130
220
|
server.tool(reg.name, reg.description, reg.inputShape, async (input) => {
|
|
221
|
+
const handler = await resolveHandler(reg.name);
|
|
131
222
|
const result = await handler(input);
|
|
132
223
|
return {
|
|
133
224
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* capture_lead — capture a contact-form lead.
|
|
3
3
|
*
|
|
4
|
-
* Validates input via Zod, forwards to POST /
|
|
4
|
+
* Validates input via Zod, forwards to POST /v1/leads/submit via
|
|
5
5
|
* @working-theory/api-client, and shapes the response into the MCP output schema.
|
|
6
6
|
* No business logic — orchestration lives in apps/api B6.
|
|
7
7
|
*
|