create-ailk 0.22.0 → 0.24.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 +27 -14
- package/component-catalog.json +149 -5
- package/component-catalog.md +7 -6
- package/dist/add-module.js +17 -10
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +301 -13
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +7 -5
- package/dist/derive-architecture.js +26 -32
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +0 -4
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +36 -2
- package/dist/lib/scaffold-render-sweep.js +50 -8
- package/dist/module-architecture.js +115 -10
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/spoke-checkout.d.ts +38 -35
- package/dist/spoke-checkout.js +66 -229
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +33 -12
- package/dist/sync-routes.d.ts +68 -5
- package/dist/sync-routes.js +455 -29
- package/dist/template-record.d.ts +1 -1
- package/dist/template-record.js +3 -2
- package/dist/update.js +10 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +13 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/CLAUDE.md +4 -2
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +34 -0
- package/templates/apps/api/CLAUDE.md +94 -19
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -1
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +16 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/site.ts +4 -1
- package/templates/apps/api/src/lib/stripe.ts +77 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +127 -0
- package/templates/apps/api/src/openapi/spec.ts +343 -7
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +332 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1426 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +4 -2
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/get-page.ts +34 -119
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -36
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/search.ts +16 -45
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/index.ts +377 -308
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +20 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +17 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +1 -1
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +6 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +93 -17
- package/templates/apps/api/src/server.ts +165 -51
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +121 -4
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +61 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
- package/templates/apps/api/src/services/__tests__/person.test.ts +260 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +450 -0
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +202 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +15 -18
- package/templates/apps/api/src/services/abandon-email.ts +28 -5
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +15 -12
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +113 -58
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +348 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +975 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +400 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +685 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +318 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +403 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1083 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +147 -9
- package/templates/apps/api/src/services/plan-offerings.ts +96 -30
- package/templates/apps/api/src/services/project-listings.ts +43 -31
- package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
- package/templates/apps/mcp/CLAUDE.md +32 -18
- package/templates/apps/mcp/__tests__/app-entry.test.ts +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +95 -14
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +8 -3
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -25
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +22 -7
- package/templates/apps/mcp/__tests__/list_pages.test.ts +84 -44
- package/templates/apps/mcp/__tests__/locale.test.ts +41 -56
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +189 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +115 -78
- package/templates/apps/mcp/__tests__/request_callback.test.ts +63 -128
- package/templates/apps/mcp/__tests__/search_content.test.ts +76 -61
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +82 -167
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +59 -130
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/package.json +1 -0
- package/templates/apps/mcp/src/abuse/index.ts +18 -11
- package/templates/apps/mcp/src/http-transport.ts +47 -9
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -26
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +5 -4
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/lib/site-config.ts +53 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +30 -28
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/index.ts +22 -21
- package/templates/apps/mcp/src/tools/list_pages.ts +27 -33
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/tools/read_content.ts +46 -40
- package/templates/apps/mcp/src/tools/request_callback.ts +21 -28
- package/templates/apps/mcp/src/tools/search_content.ts +20 -24
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +23 -26
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +10 -31
- package/templates/apps/web/CLAUDE.md +324 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +20 -6
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +369 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +64 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +9 -20
- package/templates/apps/web/lib/deck-page.ts +1 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +44 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +205 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
- package/templates/apps/web/messages/en.json +49 -15
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +51 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +63 -1
- package/templates/database/__tests__/trial-magnet-migration.test.ts +186 -0
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +28 -0
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +23 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +57 -2
- package/templates/tools/lint-architecture.ts +205 -1
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
- package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/checkout.ts +0 -93
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -14,13 +14,35 @@
|
|
|
14
14
|
* offering fields on its own frontmatter answer instead — a listing that
|
|
15
15
|
* declares `price` + `priceCurrency` yields one tier named for the page. A
|
|
16
16
|
* listing with neither an authored offer nor a price still returns `null`: no
|
|
17
|
-
* price means no tier, never a fabricated zero.
|
|
17
|
+
* price means no tier, never a fabricated zero. `getPricingTiers`
|
|
18
|
+
* (`@working-theory/content-adapters`) documents the mapping.
|
|
19
|
+
*
|
|
20
|
+
* HTTP twin (#7178): `GET /v1/content/pricing` in `apps/api` takes the same
|
|
21
|
+
* arguments and answers through the same `getPricingTiers` — the shared seam
|
|
22
|
+
* the pair proves parity at. The tool still reads content locally, so it
|
|
23
|
+
* needs no API to answer.
|
|
24
|
+
*
|
|
25
|
+
* Offerings (#8933, spec D8): when the site sells through its connected
|
|
26
|
+
* Stripe catalog and `slug` is omitted or `pricing`, the tool answers the
|
|
27
|
+
* workspace's Offerings shown set instead — read over HTTP by
|
|
28
|
+
* `lib/shown-offerings.ts` (D6: only on a `claimed` host, at most
|
|
29
|
+
* `MCP_SHOWN_SET_BUDGET_MS`) and mapped by `pricingTiersFromShownSet`, the
|
|
30
|
+
* function the twin route answers through too, so each tier also carries
|
|
31
|
+
* `interval` and the full `lookupKey`. Names, descriptions and prices are
|
|
32
|
+
* seller-provided data returned as plain JSON values (T5). Any other slug, an
|
|
33
|
+
* unclaimed host, a workspace with no current set, a failed read or a mapping
|
|
34
|
+
* that throws answers the page content below, unchanged.
|
|
18
35
|
*
|
|
19
36
|
* Input: { slug?: string, locale?: string }
|
|
20
|
-
* Output: { tiers: Array<{ name, price, priceCurrency, description? }> } | null
|
|
37
|
+
* Output: { tiers: Array<{ name, price, priceCurrency, description?, interval?, lookupKey? }> } | null
|
|
21
38
|
* Session/scope: none — read-only public content.
|
|
22
39
|
*/
|
|
23
40
|
|
|
41
|
+
import {
|
|
42
|
+
getPricingTiers,
|
|
43
|
+
pricingTiersFromShownSet,
|
|
44
|
+
shownSetAnswersPricing,
|
|
45
|
+
} from "@working-theory/content-adapters";
|
|
24
46
|
import {
|
|
25
47
|
getPricingInputSchema,
|
|
26
48
|
getPricingOutputSchema,
|
|
@@ -33,12 +55,14 @@ import {
|
|
|
33
55
|
mcpContentRoot,
|
|
34
56
|
} from "../lib/content-adapter.js";
|
|
35
57
|
import { resolveLocale } from "../lib/resolve-locale.js";
|
|
58
|
+
import {
|
|
59
|
+
mcpCurrentShownSet,
|
|
60
|
+
reportMappingFailure,
|
|
61
|
+
} from "../lib/shown-offerings.js";
|
|
36
62
|
|
|
37
63
|
export { getPricingInputSchema, getPricingOutputSchema };
|
|
38
64
|
export type { GetPricingInput, GetPricingOutput };
|
|
39
65
|
|
|
40
|
-
const DEFAULT_SLUG = "pricing";
|
|
41
|
-
|
|
42
66
|
/**
|
|
43
67
|
* get_pricing handler.
|
|
44
68
|
*
|
|
@@ -51,44 +75,20 @@ export async function ailkGetPricing(
|
|
|
51
75
|
): Promise<GetPricingOutput> {
|
|
52
76
|
getPricingInputSchema.parse(input);
|
|
53
77
|
|
|
78
|
+
if (shownSetAnswersPricing(input.slug)) {
|
|
79
|
+
const shownSet = await mcpCurrentShownSet();
|
|
80
|
+
if (shownSet !== null) {
|
|
81
|
+
try {
|
|
82
|
+
return pricingTiersFromShownSet(shownSet);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
reportMappingFailure(err);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
54
89
|
const root = contentRoot ?? mcpContentRoot();
|
|
55
90
|
const locale = resolveLocale(input.locale);
|
|
56
91
|
const adapter = getMcpContentAdapter({ contentRoot: root, locale });
|
|
57
92
|
|
|
58
|
-
|
|
59
|
-
const data = await adapter.extractStructuredData("Product", slug, locale);
|
|
60
|
-
|
|
61
|
-
const schema = (data ?? {}) as Record<string, unknown>;
|
|
62
|
-
|
|
63
|
-
// Support both a top-level `offers` array and a single offer object.
|
|
64
|
-
const offersRaw = schema["offers"];
|
|
65
|
-
const offersArr: Record<string, unknown>[] = Array.isArray(offersRaw)
|
|
66
|
-
? (offersRaw as Record<string, unknown>[])
|
|
67
|
-
: offersRaw && typeof offersRaw === "object"
|
|
68
|
-
? [offersRaw as Record<string, unknown>]
|
|
69
|
-
: [];
|
|
70
|
-
|
|
71
|
-
if (offersArr.length === 0) {
|
|
72
|
-
const page = await adapter.getPageBySlug("Product", slug, locale);
|
|
73
|
-
if (!page || page.price === undefined) return null;
|
|
74
|
-
return getPricingOutputSchema.parse({
|
|
75
|
-
tiers: [
|
|
76
|
-
{
|
|
77
|
-
name: page.title,
|
|
78
|
-
price: page.price,
|
|
79
|
-
priceCurrency: page.priceCurrency ?? "USD",
|
|
80
|
-
...(page.summary !== undefined && { description: page.summary }),
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const tiers = offersArr.map((offer) => ({
|
|
87
|
-
name: (offer["name"] as string) ?? "",
|
|
88
|
-
price: (offer["price"] as string | number) ?? "",
|
|
89
|
-
priceCurrency: (offer["priceCurrency"] as string) ?? "USD",
|
|
90
|
-
description: offer["description"] as string | undefined,
|
|
91
|
-
}));
|
|
92
|
-
|
|
93
|
-
return getPricingOutputSchema.parse({ tiers });
|
|
93
|
+
return getPricingTiers(adapter, { slug: input.slug, locale });
|
|
94
94
|
}
|
|
@@ -13,27 +13,29 @@ export {
|
|
|
13
13
|
} from "./purchase.js";
|
|
14
14
|
|
|
15
15
|
export {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
contentListParamsSchema,
|
|
17
|
+
contentListQuerySchema,
|
|
18
|
+
contentListResultSchema,
|
|
18
19
|
ailkListPages,
|
|
19
20
|
type ListPagesInput,
|
|
20
|
-
type
|
|
21
|
+
type ContentListResult,
|
|
21
22
|
} from "./list_pages.js";
|
|
22
23
|
|
|
23
24
|
export {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
contentPageParamsSchema,
|
|
26
|
+
contentPageQuerySchema,
|
|
27
|
+
contentPageResultSchema,
|
|
26
28
|
ailkReadContent,
|
|
27
29
|
type ReadContentInput,
|
|
28
|
-
type
|
|
30
|
+
type ContentPageResult,
|
|
29
31
|
} from "./read_content.js";
|
|
30
32
|
|
|
31
33
|
export {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
contentSearchQuerySchema,
|
|
35
|
+
contentSearchResultSchema,
|
|
34
36
|
ailkSearchContent,
|
|
35
37
|
type SearchContentInput,
|
|
36
|
-
type
|
|
38
|
+
type ContentSearchResult,
|
|
37
39
|
} from "./search_content.js";
|
|
38
40
|
|
|
39
41
|
export {
|
|
@@ -92,28 +94,27 @@ export {
|
|
|
92
94
|
type AuditOutput,
|
|
93
95
|
} from "./audit.js";
|
|
94
96
|
|
|
97
|
+
// #7171, #7172, #7173 — the three lead writes take their routes' body
|
|
98
|
+
// exports and share one output schema (the route's 201 body or the tool's
|
|
99
|
+
// error), re-exported once from capture_lead.
|
|
95
100
|
export {
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
leadSubmitBodySchema,
|
|
102
|
+
leadWriteOutputSchema,
|
|
98
103
|
ailkCaptureLead,
|
|
99
|
-
type
|
|
100
|
-
type
|
|
104
|
+
type LeadSubmitBody,
|
|
105
|
+
type LeadWriteOutput,
|
|
101
106
|
} from "./capture_lead.js";
|
|
102
107
|
|
|
103
108
|
export {
|
|
104
|
-
|
|
105
|
-
subscribeNewsletterOutputSchema,
|
|
109
|
+
leadSubscribeBodySchema,
|
|
106
110
|
ailkSubscribeNewsletter,
|
|
107
|
-
type
|
|
108
|
-
type SubscribeNewsletterOutput,
|
|
111
|
+
type LeadSubscribeBody,
|
|
109
112
|
} from "./subscribe_newsletter.js";
|
|
110
113
|
|
|
111
114
|
export {
|
|
112
|
-
|
|
113
|
-
requestCallbackOutputSchema,
|
|
115
|
+
leadCallbackBodySchema,
|
|
114
116
|
ailkRequestCallback,
|
|
115
|
-
type
|
|
116
|
-
type RequestCallbackOutput,
|
|
117
|
+
type LeadCallbackBody,
|
|
117
118
|
} from "./request_callback.js";
|
|
118
119
|
|
|
119
120
|
export {
|
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* list_pages — returns published content pages
|
|
2
|
+
* list_pages — returns one page of the published content pages of a type.
|
|
3
3
|
*
|
|
4
4
|
* Pure read: resolves its adapter through `getMcpContentAdapter()` — the one
|
|
5
|
-
* resolver seam (#5805, ADR 0011), MDX by default
|
|
5
|
+
* resolver seam (#5805, ADR 0011), MDX by default — and answers through
|
|
6
|
+
* `listPagesItemList` (`@working-theory/content-adapters`).
|
|
6
7
|
* No auth, no side effects, no network. The adapter reads from the filesystem.
|
|
7
8
|
*
|
|
9
|
+
* HTTP twin (#7168): `GET /v1/content/:type` in `apps/api` parses the same
|
|
10
|
+
* `contentListParamsSchema` + `contentListQuerySchema` and answers through
|
|
11
|
+
* the same builder — the tool adopted the API, arguments and results: `type`
|
|
12
|
+
* is required, and `page` (default 1) / `pageSize` (default 20, at most 100)
|
|
13
|
+
* pick the window.
|
|
14
|
+
*
|
|
8
15
|
* Phase 3 S3: optional `locale` parameter. Validated against `readLocales()`
|
|
9
16
|
* allowlist; invalid or unknown values silently fall back to the project's
|
|
10
17
|
* default locale (AC-i18n-07). Lists are locale-scoped — pages must exist in
|
|
11
18
|
* the requested locale's directory to be enumerated.
|
|
12
19
|
*
|
|
13
|
-
* Input: { type
|
|
14
|
-
* Output: {
|
|
20
|
+
* Input: { type: SchemaOrgType, page?: number, pageSize?: number, locale?: string }
|
|
21
|
+
* Output: { "@context", "@type": "ItemList", type, items: ContentPage[],
|
|
22
|
+
* pagination: { page, pageSize, totalItems, totalPages } }
|
|
15
23
|
* Session/scope: none — read-only public content.
|
|
16
24
|
*/
|
|
17
25
|
|
|
26
|
+
import { listPagesItemList } from "@working-theory/content-adapters";
|
|
18
27
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
listPagesInputSchema,
|
|
24
|
-
listPagesOutputSchema,
|
|
28
|
+
contentListParamsSchema,
|
|
29
|
+
contentListQuerySchema,
|
|
30
|
+
contentListResultSchema,
|
|
31
|
+
type ContentListResult,
|
|
25
32
|
type ListPagesInput,
|
|
26
|
-
type ListPagesOutput,
|
|
27
33
|
} from "@working-theory/validation";
|
|
28
34
|
|
|
29
35
|
import {
|
|
@@ -32,8 +38,12 @@ import {
|
|
|
32
38
|
} from "../lib/content-adapter.js";
|
|
33
39
|
import { resolveLocale } from "../lib/resolve-locale.js";
|
|
34
40
|
|
|
35
|
-
export {
|
|
36
|
-
|
|
41
|
+
export {
|
|
42
|
+
contentListParamsSchema,
|
|
43
|
+
contentListQuerySchema,
|
|
44
|
+
contentListResultSchema,
|
|
45
|
+
};
|
|
46
|
+
export type { ListPagesInput, ContentListResult };
|
|
37
47
|
|
|
38
48
|
/**
|
|
39
49
|
* list_pages handler.
|
|
@@ -45,30 +55,14 @@ export type { ListPagesInput, ListPagesOutput };
|
|
|
45
55
|
export async function ailkListPages(
|
|
46
56
|
input: ListPagesInput,
|
|
47
57
|
contentRoot?: string,
|
|
48
|
-
): Promise<
|
|
49
|
-
|
|
58
|
+
): Promise<ContentListResult> {
|
|
59
|
+
const { type } = contentListParamsSchema.parse(input);
|
|
60
|
+
const { page, pageSize } = contentListQuerySchema.parse(input);
|
|
50
61
|
|
|
51
62
|
// Default: the one content-root resolver (#8070 D3).
|
|
52
63
|
const root = contentRoot ?? mcpContentRoot();
|
|
53
64
|
const locale = resolveLocale(input.locale);
|
|
54
65
|
const adapter = getMcpContentAdapter({ contentRoot: root, locale });
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
? [input.type]
|
|
58
|
-
: SCHEMA_ORG_TYPES;
|
|
59
|
-
|
|
60
|
-
const perType = await Promise.all(
|
|
61
|
-
types.map((type) => adapter.listPages(type, locale)),
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const pages: ListPagesOutput["pages"] = perType.flatMap((results) =>
|
|
65
|
-
results.map((page) => ({
|
|
66
|
-
slug: page.slug,
|
|
67
|
-
type: page.type,
|
|
68
|
-
title: page.title,
|
|
69
|
-
...(page.description !== undefined && { description: page.description }),
|
|
70
|
-
})),
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return listPagesOutputSchema.parse({ pages });
|
|
67
|
+
return listPagesItemList(adapter, { type, page, pageSize, locale });
|
|
74
68
|
}
|
|
@@ -57,11 +57,24 @@
|
|
|
57
57
|
* through `@working-theory/api-client` per the architecture invariants (apps
|
|
58
58
|
* may not import each other; the shared HTTP surface lives in packages/).
|
|
59
59
|
*
|
|
60
|
-
*
|
|
60
|
+
* THE KEY — the full Offerings lookup_key (#8935). `productCode` is a price's
|
|
61
|
+
* full lookup_key (contract §3.7), the `lookupKey` `get_pricing` and
|
|
62
|
+
* `get_offerings` return. After the claim and the strict schema, the one
|
|
63
|
+
* §3.7 parser, `parseLookupKey`, refuses an ungrammatical key before any HTTP
|
|
64
|
+
* call, with the same not-found body every route 404 maps to (A3, A5). The
|
|
65
|
+
* workspace stays the server's (A1): the api-client attaches the
|
|
66
|
+
* deployment's own spoke key, and the route alone decides whether the key is
|
|
67
|
+
* shown and org-enabled. A spoke whose Organization has no Stripe connection
|
|
68
|
+
* gets the route's same 404, never a session on the hub's own env key, so
|
|
69
|
+
* this tool refuses it as the web buy route does (#9126).
|
|
70
|
+
*
|
|
71
|
+
* Spec: `docs/specs/5971-start-purchase-hosted-checkout-spec.md`,
|
|
72
|
+
* `docs/specs/8935-start-purchase-sells-a-named-spec.md`.
|
|
61
73
|
*/
|
|
62
74
|
|
|
63
75
|
import { checkout } from "@working-theory/api-client";
|
|
64
76
|
import {
|
|
77
|
+
parseLookupKey,
|
|
65
78
|
purchaseMcpInputSchema,
|
|
66
79
|
type PurchaseMcpInput,
|
|
67
80
|
type PurchaseOutput,
|
|
@@ -83,6 +96,15 @@ function commerceDisabledBody(): ErrorBody {
|
|
|
83
96
|
return notFoundError("Not found");
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The one not-found body for an offering (#8935 D2, A5): an ungrammatical key
|
|
101
|
+
* refused before the call and every route 404 answer this same value, and
|
|
102
|
+
* neither echoes the caller's key.
|
|
103
|
+
*/
|
|
104
|
+
function offeringNotFoundBody(): ErrorBody {
|
|
105
|
+
return notFoundError("No offering matches that product code.");
|
|
106
|
+
}
|
|
107
|
+
|
|
86
108
|
/**
|
|
87
109
|
* Export name `ailkPurchase` satisfies the parity-test naming convention
|
|
88
110
|
* (ailk<PascalVerb>) so the harness can discover it.
|
|
@@ -127,6 +149,12 @@ export async function ailkPurchase(
|
|
|
127
149
|
|
|
128
150
|
const { productCode, quantity } = parsed.data;
|
|
129
151
|
|
|
152
|
+
// #8935 D2 — a key outside the §3.7 grammar can never sell, so it is
|
|
153
|
+
// refused here, before the route spends a rate-limit token on it.
|
|
154
|
+
if (parseLookupKey(productCode) === null) {
|
|
155
|
+
return offeringNotFoundBody();
|
|
156
|
+
}
|
|
157
|
+
|
|
130
158
|
// successUrl/cancelUrl are ALWAYS derived from baseUrl (#6001) — never
|
|
131
159
|
// accepted from the caller. allowPromotionCodes is never forwarded: it is
|
|
132
160
|
// not part of purchaseMcpInputSchema, so it can never be present here.
|
|
@@ -152,7 +180,7 @@ export async function ailkPurchase(
|
|
|
152
180
|
// states and tests that invariant ("Error responses never echo user
|
|
153
181
|
// input", apps/api/src/routes/checkout/sessions.ts), and parity means
|
|
154
182
|
// the pair answers alike.
|
|
155
|
-
return
|
|
183
|
+
return offeringNotFoundBody();
|
|
156
184
|
}
|
|
157
185
|
return validationError(
|
|
158
186
|
`checkout-unavailable: POST /v1/checkout/sessions returned ${result.status}`,
|
|
@@ -1,40 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* read_content — returns a single page's
|
|
2
|
+
* read_content — returns a single page's document: its schema.org JSON-LD at
|
|
3
|
+
* the root, its frontmatter under `page`, and the raw MDX body on request.
|
|
3
4
|
*
|
|
4
|
-
* Pure read: resolves its adapter through `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Pure read: resolves its adapter through `getMcpContentAdapter()` — the one
|
|
6
|
+
* resolver seam (#5805, ADR 0011), MDX by default — and answers through
|
|
7
|
+
* `readContentDocument` (`@working-theory/content-adapters`), over the
|
|
8
|
+
* deployment's `_site.mdx` (`lib/site-config.ts`). A missing page is the MCP
|
|
9
|
+
* not-found body (`lib/errors.ts`), carrying the route's 404 message — never
|
|
10
|
+
* a throw.
|
|
11
|
+
*
|
|
12
|
+
* HTTP twin (#7169): `GET /v1/content/:type/:slug` in `apps/api` parses the
|
|
13
|
+
* same `contentPageParamsSchema` + `contentPageQuerySchema` and answers
|
|
14
|
+
* through the same builder — the tool adopted the API, arguments and results:
|
|
15
|
+
* `body` is served only with `include: "body"`, from ONE `getPageSource`
|
|
16
|
+
* read (#5927). The builder's own capability check replaces the retired
|
|
17
|
+
* MDX-narrowed resolver twin this tool used to call: every adapter the
|
|
18
|
+
* resolver returns carries `getPageSource`, the fail-closed empty one
|
|
19
|
+
* included (#5876 D5).
|
|
11
20
|
*
|
|
12
21
|
* Phase 3 S3: optional `locale` parameter. Validated against `readLocales()`
|
|
13
22
|
* allowlist; invalid or unknown values silently fall back to the project's
|
|
14
23
|
* default locale (AC-i18n-07). The adapter applies its own fallback chain.
|
|
15
24
|
*
|
|
16
|
-
* Input: { type: SchemaOrgType, slug: string, locale?: string
|
|
17
|
-
*
|
|
18
|
-
* @working-theory/validation's
|
|
19
|
-
*
|
|
25
|
+
* Input: { type: SchemaOrgType, slug: string, locale?: string,
|
|
26
|
+
* include?: "body" } — `type` admits the full SchemaOrgType set
|
|
27
|
+
* (#6030 D1), mirrored in @working-theory/validation's
|
|
28
|
+
* MCP_SCHEMA_ORG_TYPES.
|
|
29
|
+
* Output: { ...jsonLd, page, body? } | { error: "not_found", message }
|
|
20
30
|
* Session/scope: none — read-only public content.
|
|
21
31
|
*/
|
|
22
32
|
|
|
33
|
+
import { readContentDocument } from "@working-theory/content-adapters";
|
|
23
34
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
contentPageParamsSchema,
|
|
36
|
+
contentPageQuerySchema,
|
|
37
|
+
contentPageResultSchema,
|
|
38
|
+
type ContentPageResult,
|
|
26
39
|
type ReadContentInput,
|
|
27
|
-
type ReadContentOutput,
|
|
28
40
|
} from "@working-theory/validation";
|
|
29
41
|
|
|
30
42
|
import {
|
|
31
|
-
|
|
43
|
+
getMcpContentAdapter,
|
|
32
44
|
mcpContentRoot,
|
|
33
45
|
} from "../lib/content-adapter.js";
|
|
46
|
+
import { notFoundError, type ErrorBody } from "../lib/errors.js";
|
|
34
47
|
import { resolveLocale } from "../lib/resolve-locale.js";
|
|
48
|
+
import { readSiteConfig } from "../lib/site-config.js";
|
|
35
49
|
|
|
36
|
-
export {
|
|
37
|
-
|
|
50
|
+
export {
|
|
51
|
+
contentPageParamsSchema,
|
|
52
|
+
contentPageQuerySchema,
|
|
53
|
+
contentPageResultSchema,
|
|
54
|
+
};
|
|
55
|
+
export type { ReadContentInput, ContentPageResult };
|
|
38
56
|
|
|
39
57
|
/**
|
|
40
58
|
* read_content handler.
|
|
@@ -45,31 +63,19 @@ export type { ReadContentInput, ReadContentOutput };
|
|
|
45
63
|
export async function ailkReadContent(
|
|
46
64
|
input: ReadContentInput,
|
|
47
65
|
contentRoot?: string,
|
|
48
|
-
): Promise<
|
|
49
|
-
|
|
66
|
+
): Promise<ContentPageResult | ErrorBody> {
|
|
67
|
+
const { type, slug } = contentPageParamsSchema.parse(input);
|
|
68
|
+
const { include } = contentPageQuerySchema.parse(input);
|
|
50
69
|
|
|
51
70
|
// Default: the one content-root resolver (#8070 D3).
|
|
52
71
|
const root = contentRoot ?? mcpContentRoot();
|
|
53
72
|
const locale = resolveLocale(input.locale);
|
|
73
|
+
const adapter = getMcpContentAdapter({ contentRoot: root, locale });
|
|
54
74
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return
|
|
61
|
-
// Prefer the stored frontmatter's slug/type; fall back to the
|
|
62
|
-
// request-validated input (#5876 spec D9). On the MDX path the
|
|
63
|
-
// frontmatter always carries both — required, non-optional fields of
|
|
64
|
-
// FrontmatterSchema — so this is inert there and the mdx payload stays
|
|
65
|
-
// byte-identical. The fallback matters for cross-adapter identity: the
|
|
66
|
-
// Epic #5324 AC-WCE-04 comparison needs `read_content`'s payload to be
|
|
67
|
-
// identical across adapters, which a request-validated value guarantees
|
|
68
|
-
// and a stored-bookkeeping value only guarantees if the write path
|
|
69
|
-
// agrees.
|
|
70
|
-
slug: source.frontmatter.slug ?? input.slug,
|
|
71
|
-
type: source.frontmatter.type ?? input.type,
|
|
72
|
-
frontmatter: source.frontmatter as Record<string, unknown>,
|
|
73
|
-
body: source.body,
|
|
74
|
-
});
|
|
75
|
+
const document = await readContentDocument(
|
|
76
|
+
adapter,
|
|
77
|
+
{ type, slug, locale, include },
|
|
78
|
+
readSiteConfig(root),
|
|
79
|
+
);
|
|
80
|
+
return document ?? notFoundError("Page not found");
|
|
75
81
|
}
|
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* request_callback —
|
|
2
|
+
* request_callback — request a callback: the body `POST /v1/leads/callback`
|
|
3
|
+
* takes.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The tool adopted the API, arguments and results (#7173 — the operator's
|
|
6
|
+
* decision of 2026-10-19). Its input IS the route's `leadCallbackBodySchema`
|
|
7
|
+
* (`{ name, phone, preferredTime?, payload? }`; the catalog registers the
|
|
8
|
+
* same shape, so the SDK has already parsed the arguments against it — the
|
|
9
|
+
* `safeParse` here keeps the stdio path and the `invalid_input` branch
|
|
10
|
+
* honest), the parsed body is posted as the agent sent it — no `siteId`, no
|
|
11
|
+
* stamped `payload: {}` — and the route's 201 body `{ id, persisted, email }`
|
|
12
|
+
* comes back unchanged. No business logic — orchestration lives in apps/api.
|
|
7
13
|
*
|
|
8
|
-
* Input: { name, phone, preferredTime? }
|
|
9
|
-
* Output: { ok: true, id, persisted } | { ok: false, error: { kind, ... } }
|
|
10
14
|
* Session/scope: none — anonymous write, no auth required.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
import { apiClient } from "@working-theory/api-client";
|
|
14
18
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
type RequestCallbackOutput,
|
|
19
|
+
leadCallbackBodySchema,
|
|
20
|
+
type LeadCallbackBody,
|
|
21
|
+
type LeadWriteOutput,
|
|
19
22
|
} from "@working-theory/validation";
|
|
20
23
|
|
|
21
24
|
import { visitorForwardHeadersForThisCall } from "../request-context.js";
|
|
22
25
|
|
|
23
|
-
export {
|
|
24
|
-
export type {
|
|
26
|
+
export { leadCallbackBodySchema };
|
|
27
|
+
export type { LeadCallbackBody };
|
|
25
28
|
|
|
26
29
|
export async function ailkRequestCallback(
|
|
27
|
-
input:
|
|
28
|
-
): Promise<
|
|
29
|
-
const parsed =
|
|
30
|
+
input: LeadCallbackBody,
|
|
31
|
+
): Promise<LeadWriteOutput> {
|
|
32
|
+
const parsed = leadCallbackBodySchema.safeParse(input);
|
|
30
33
|
if (!parsed.success) {
|
|
31
34
|
return {
|
|
32
35
|
ok: false,
|
|
@@ -34,26 +37,16 @@ export async function ailkRequestCallback(
|
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
const { name, phone, preferredTime } = parsed.data;
|
|
38
|
-
const siteId = process.env.AILK_SITE_ID ?? "default";
|
|
39
|
-
const body = {
|
|
40
|
-
siteId,
|
|
41
|
-
name,
|
|
42
|
-
phone,
|
|
43
|
-
...(preferredTime !== undefined && { preferredTime }),
|
|
44
|
-
payload: {},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
40
|
// #8073 — forward the address the transport trusted (never an identity
|
|
48
41
|
// argument on this tool's own schema) so the hub counts the write in that
|
|
49
42
|
// visitor's own rate-limit bucket rather than this MCP host's.
|
|
50
43
|
const headers = visitorForwardHeadersForThisCall();
|
|
51
44
|
const res = headers
|
|
52
|
-
? await apiClient.leads.callback(
|
|
53
|
-
: await apiClient.leads.callback(
|
|
45
|
+
? await apiClient.leads.callback(parsed.data, { headers })
|
|
46
|
+
: await apiClient.leads.callback(parsed.data);
|
|
54
47
|
if (!res.ok) {
|
|
55
48
|
return { ok: false, error: { kind: "api_error", status: res.status } };
|
|
56
49
|
}
|
|
57
50
|
|
|
58
|
-
return
|
|
51
|
+
return res.body;
|
|
59
52
|
}
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
* search_content — full-text search across published content pages.
|
|
3
3
|
*
|
|
4
4
|
* Pure read: resolves its adapter through `getMcpContentAdapter()` — the one
|
|
5
|
-
* resolver seam (#5805, ADR 0011), MDX by default
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* resolver seam (#5805, ADR 0011), MDX by default — and answers through
|
|
6
|
+
* `searchContentItemList` (`@working-theory/content-adapters`), which scores by
|
|
7
|
+
* title/description/body match through `adapter.searchPages`.
|
|
8
|
+
*
|
|
9
|
+
* HTTP twin (#7167): `GET /v1/content/search` in `apps/api` parses the same
|
|
10
|
+
* `contentSearchQuerySchema` and answers through the same builder — the tool
|
|
11
|
+
* adopted the API, arguments and results. Limit defaults to 10; max 50. The
|
|
12
|
+
* search string `q` is capped at 200 chars (enforced by Zod).
|
|
8
13
|
*
|
|
9
14
|
* Phase 3 S3: optional `locale` parameter. Search is locale-scoped — only the
|
|
10
15
|
* requested locale's content directory is searched. Invalid or unknown values
|
|
@@ -14,17 +19,19 @@
|
|
|
14
19
|
* registry. Unlike `locale`, an unrecognised value is a validation error, not
|
|
15
20
|
* a silent fallback: the registry is closed, so a bad slug is a caller mistake.
|
|
16
21
|
*
|
|
17
|
-
* Input: {
|
|
22
|
+
* Input: { q: string, type?: SchemaOrgType, category?: CategorySlug,
|
|
18
23
|
* limit?: number, locale?: string }
|
|
19
|
-
* Output: {
|
|
24
|
+
* Output: { "@context", "@type": "ItemList", query, items: Array<{ slug, type,
|
|
25
|
+
* title, snippet, score }>, totalItems }
|
|
20
26
|
* Session/scope: none — read-only public content.
|
|
21
27
|
*/
|
|
22
28
|
|
|
29
|
+
import { searchContentItemList } from "@working-theory/content-adapters";
|
|
23
30
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
contentSearchQuerySchema,
|
|
32
|
+
contentSearchResultSchema,
|
|
33
|
+
type ContentSearchResult,
|
|
26
34
|
type SearchContentInput,
|
|
27
|
-
type SearchContentOutput,
|
|
28
35
|
} from "@working-theory/validation";
|
|
29
36
|
|
|
30
37
|
import {
|
|
@@ -33,10 +40,8 @@ import {
|
|
|
33
40
|
} from "../lib/content-adapter.js";
|
|
34
41
|
import { resolveLocale } from "../lib/resolve-locale.js";
|
|
35
42
|
|
|
36
|
-
export {
|
|
37
|
-
export type { SearchContentInput,
|
|
38
|
-
|
|
39
|
-
const DEFAULT_LIMIT = 10;
|
|
43
|
+
export { contentSearchQuerySchema, contentSearchResultSchema };
|
|
44
|
+
export type { SearchContentInput, ContentSearchResult };
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
47
|
* search_content handler.
|
|
@@ -47,21 +52,12 @@ const DEFAULT_LIMIT = 10;
|
|
|
47
52
|
export async function ailkSearchContent(
|
|
48
53
|
input: SearchContentInput,
|
|
49
54
|
contentRoot?: string,
|
|
50
|
-
): Promise<
|
|
51
|
-
|
|
55
|
+
): Promise<ContentSearchResult> {
|
|
56
|
+
const { q, type, category, limit } = contentSearchQuerySchema.parse(input);
|
|
52
57
|
|
|
53
58
|
const root = contentRoot ?? mcpContentRoot();
|
|
54
59
|
const locale = resolveLocale(input.locale);
|
|
55
60
|
const adapter = getMcpContentAdapter({ contentRoot: root, locale });
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
limit: input.limit ?? DEFAULT_LIMIT,
|
|
59
|
-
...(input.type !== undefined && { type: input.type }),
|
|
60
|
-
...(input.category !== undefined && { category: input.category }),
|
|
61
|
-
locale,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const rawResults = await adapter.searchPages(input.query, options);
|
|
65
|
-
|
|
66
|
-
return searchContentOutputSchema.parse({ results: rawResults });
|
|
62
|
+
return searchContentItemList(adapter, { q, type, category, limit, locale });
|
|
67
63
|
}
|