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,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paid-module-bundle tar.gz extract, with path-traversal protection (K1,
|
|
3
|
+
* #5089 — Flavor 3).
|
|
4
|
+
*
|
|
5
|
+
* Security posture is a line-for-line port of
|
|
6
|
+
* `packages/cli/src/lib/extract-bundle.ts` (`extractBundle`) — fail-closed on
|
|
7
|
+
* every unsafe entry:
|
|
8
|
+
* - Reject entries whose normalized path escapes the staging root (any
|
|
9
|
+
* `..` segment, absolute paths, or any path that resolves outside).
|
|
10
|
+
* - Reject ANY symlink / hardlink entry.
|
|
11
|
+
* - Cap extracted-entry count + cumulative size to prevent zip-bomb-style
|
|
12
|
+
* denial-of-service.
|
|
13
|
+
* - Strip setuid/setgid mode bits.
|
|
14
|
+
*
|
|
15
|
+
* NOT a straight import of `extractBundle` — see `fetch-module.ts`'s file
|
|
16
|
+
* doc for why `create-ailk` (the one publicly-published package in this
|
|
17
|
+
* monorepo) cannot take a runtime dependency on anything in
|
|
18
|
+
* `packages/cli/src/lib/`, which is `publishConfig.access: "restricted"`.
|
|
19
|
+
* This is a deliberate, named duplication of the guard logic, not a
|
|
20
|
+
* reimplementation of a different one.
|
|
21
|
+
*
|
|
22
|
+
* ONE difference from `extractBundle`, required by the caller's shape:
|
|
23
|
+
* `extractBundle` extracts into an EMPTY staging dir that then swap-renames
|
|
24
|
+
* over the (possibly pre-existing) install target — appropriate for a single
|
|
25
|
+
* self-contained skill directory. A fetched module's files are scattered
|
|
26
|
+
* across an ALREADY-SCAFFOLDED tree (`apps/api/src/routes/...`,
|
|
27
|
+
* `apps/web/app/...`), so there is no single directory to rename over.
|
|
28
|
+
* `extractModuleBundle` therefore extracts into a throwaway temp dir first
|
|
29
|
+
* (same validation, same guards), and ONLY on full success copies each
|
|
30
|
+
* validated file into its place under `targetDir` — so a rejected or
|
|
31
|
+
* partially-decompressed bundle never touches the scaffold at all.
|
|
32
|
+
*/
|
|
33
|
+
import { copyFileSync, createReadStream, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
34
|
+
import { tmpdir } from "node:os";
|
|
35
|
+
import { isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
36
|
+
import { pipeline } from "node:stream/promises";
|
|
37
|
+
import { createGunzip } from "node:zlib";
|
|
38
|
+
import * as tar from "tar";
|
|
39
|
+
/** Soft limits to prevent decompression-bomb / zip-bomb attacks. Same values as extractBundle. */
|
|
40
|
+
const MAX_ENTRIES = 1000;
|
|
41
|
+
const MAX_TOTAL_SIZE_BYTES = 50 * 1024 * 1024; // 50 MiB
|
|
42
|
+
/**
|
|
43
|
+
* Extract a fetched module bundle into `targetDir`, merging its files onto
|
|
44
|
+
* the tree `copyTemplate` already wrote. Throws on any unsafe entry,
|
|
45
|
+
* decompress error, or limit overrun — `targetDir` is left untouched on
|
|
46
|
+
* failure (validation completes in a throwaway staging dir before any file
|
|
47
|
+
* is copied into place).
|
|
48
|
+
*/
|
|
49
|
+
export async function extractModuleBundle(options) {
|
|
50
|
+
const { bytes, targetDir, moduleName } = options;
|
|
51
|
+
const targetAbs = resolve(targetDir);
|
|
52
|
+
if (!existsSync(targetAbs)) {
|
|
53
|
+
throw new Error(`extractModuleBundle: target directory does not exist: ${targetAbs}`);
|
|
54
|
+
}
|
|
55
|
+
const stagingDir = mkdtempSync(join(tmpdir(), `ailk-module-${moduleName}-`));
|
|
56
|
+
const stagingTar = join(stagingDir, "bundle.tar.gz");
|
|
57
|
+
try {
|
|
58
|
+
writeFileSync(stagingTar, bytes);
|
|
59
|
+
const extractedRoot = join(stagingDir, "extracted");
|
|
60
|
+
mkdirSync(extractedRoot, { recursive: true });
|
|
61
|
+
await safeExtractTarGz(stagingTar, extractedRoot, moduleName);
|
|
62
|
+
// Full validation passed — merge into targetDir. Copies only (never
|
|
63
|
+
// deletes anything): a fetched module's files ADD to the free scaffold,
|
|
64
|
+
// they never remove what the free copy already wrote.
|
|
65
|
+
copyTree(extractedRoot, targetAbs);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Recursively copy every file from `srcRoot` into `destRoot`, creating parent
|
|
73
|
+
* directories as needed. Runs ONLY after `safeExtractTarGz` has validated
|
|
74
|
+
* every entry, so no additional path-safety check is needed here — `srcRoot`
|
|
75
|
+
* is our own staging tree, not attacker-controlled input.
|
|
76
|
+
*/
|
|
77
|
+
function copyTree(srcRoot, destRoot) {
|
|
78
|
+
const stack = [srcRoot];
|
|
79
|
+
while (stack.length > 0) {
|
|
80
|
+
const dir = stack.pop();
|
|
81
|
+
if (dir === undefined)
|
|
82
|
+
break;
|
|
83
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
84
|
+
const srcPath = join(dir, entry.name);
|
|
85
|
+
const rel = relative(srcRoot, srcPath);
|
|
86
|
+
const destPath = join(destRoot, rel);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
mkdirSync(destPath, { recursive: true });
|
|
89
|
+
stack.push(srcPath);
|
|
90
|
+
}
|
|
91
|
+
else if (entry.isFile()) {
|
|
92
|
+
mkdirSync(join(destPath, ".."), { recursive: true });
|
|
93
|
+
copyFileSync(srcPath, destPath);
|
|
94
|
+
}
|
|
95
|
+
// symlinks: never present — safeExtractTarGz rejects them outright.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* `extractBundle`'s validating-filter tar extraction, ported verbatim (see
|
|
101
|
+
* file doc for why this is a port and not an import). Extracts `tarGzPath`
|
|
102
|
+
* into `targetDir`, which the caller guarantees is empty.
|
|
103
|
+
*/
|
|
104
|
+
async function safeExtractTarGz(tarGzPath, targetDir, moduleName) {
|
|
105
|
+
const resolvedTarget = resolve(targetDir);
|
|
106
|
+
let entryCount = 0;
|
|
107
|
+
let totalSize = 0;
|
|
108
|
+
const captured = { error: null };
|
|
109
|
+
const reject = (msg) => {
|
|
110
|
+
if (captured.error === null) {
|
|
111
|
+
captured.error = new Error(`module "${moduleName}": ${msg}`);
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
};
|
|
115
|
+
const validate = (entryPath, rawEntry) => {
|
|
116
|
+
if (captured.error !== null)
|
|
117
|
+
return false;
|
|
118
|
+
entryCount += 1;
|
|
119
|
+
if (entryCount > MAX_ENTRIES) {
|
|
120
|
+
return reject(`unsafe bundle: entry count exceeds ${MAX_ENTRIES}`);
|
|
121
|
+
}
|
|
122
|
+
const declaredSize = typeof rawEntry.size === "number" ? rawEntry.size : 0;
|
|
123
|
+
totalSize += declaredSize;
|
|
124
|
+
if (totalSize > MAX_TOTAL_SIZE_BYTES) {
|
|
125
|
+
return reject(`unsafe bundle: cumulative size exceeds ${MAX_TOTAL_SIZE_BYTES} bytes`);
|
|
126
|
+
}
|
|
127
|
+
if (typeof entryPath !== "string" || entryPath.length === 0) {
|
|
128
|
+
return reject("unsafe path: bundle entry has empty path");
|
|
129
|
+
}
|
|
130
|
+
if (isAbsolute(entryPath)) {
|
|
131
|
+
return reject(`unsafe path: bundle entry uses absolute path "${entryPath}"`);
|
|
132
|
+
}
|
|
133
|
+
const normalized = normalize(entryPath);
|
|
134
|
+
if (normalized === ".." ||
|
|
135
|
+
normalized.startsWith(`..${sep}`) ||
|
|
136
|
+
normalized.split(sep).includes("..")) {
|
|
137
|
+
return reject(`unsafe path: bundle entry "${entryPath}" contains path traversal`);
|
|
138
|
+
}
|
|
139
|
+
const fullPath = resolve(resolvedTarget, entryPath);
|
|
140
|
+
if (!isPathInside(resolvedTarget, fullPath)) {
|
|
141
|
+
return reject(`unsafe path: bundle entry "${entryPath}" resolves outside target`);
|
|
142
|
+
}
|
|
143
|
+
const entryType = rawEntry.type;
|
|
144
|
+
if (entryType === "SymbolicLink" ||
|
|
145
|
+
entryType === "Link" ||
|
|
146
|
+
entryType === "symlink" ||
|
|
147
|
+
entryType === "link") {
|
|
148
|
+
return reject(`unsafe link: bundle entry "${entryPath}" is a ${entryType} (rejected)`);
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
};
|
|
152
|
+
const extractStream = tar.x({
|
|
153
|
+
cwd: resolvedTarget,
|
|
154
|
+
strict: true,
|
|
155
|
+
filter: validate,
|
|
156
|
+
onentry: (entry) => {
|
|
157
|
+
if (typeof entry.mode === "number") {
|
|
158
|
+
entry.mode = entry.mode & 0o777;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
await pipeline(createReadStream(tarGzPath), createGunzip(), extractStream);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
if (captured.error !== null)
|
|
167
|
+
throw captured.error;
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
if (captured.error !== null) {
|
|
171
|
+
throw captured.error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** True iff `child` is the same path as `parent` or a descendant of it. */
|
|
175
|
+
function isPathInside(parent, child) {
|
|
176
|
+
const rel = relative(parent, child);
|
|
177
|
+
if (rel === "")
|
|
178
|
+
return true;
|
|
179
|
+
if (rel.startsWith(".."))
|
|
180
|
+
return false;
|
|
181
|
+
if (isAbsolute(rel))
|
|
182
|
+
return false;
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated paid-module-bundle fetch (K1, #5089 — Flavor 3).
|
|
3
|
+
*
|
|
4
|
+
* Posture lifted verbatim from `packages/cli/src/lib/fetch-bundle.ts`
|
|
5
|
+
* (`fetchBundle`, the skill-registry twin of this function):
|
|
6
|
+
* - Bearer-JWT auth on every request.
|
|
7
|
+
* - `redirect: "error"` so a maliciously redirected response can't drop us
|
|
8
|
+
* onto an unintended host — the CLI must hit the configured module
|
|
9
|
+
* distribution only.
|
|
10
|
+
* - Empty-token guard before any network call (defense in depth — the
|
|
11
|
+
* caller should already have gated on an empty/absent key).
|
|
12
|
+
* - Status-code mapping: 401 → license invalid/revoked/lapsed, 404 →
|
|
13
|
+
* module content not yet available, other 4xx/5xx → upstream body.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately DOES NOT depend on `@working-theory/license` or
|
|
16
|
+
* `@working-theory/cli` — both are `publishConfig.access: "restricted"` and
|
|
17
|
+
* `create-ailk` is the one package in this monorepo published to PUBLIC npm
|
|
18
|
+
* (Apache-2.0, A1 disposed it `oss`). A runtime dependency on a restricted
|
|
19
|
+
* package would break `pnpm install` for every free-tier consumer, not just
|
|
20
|
+
* gate the paid modules — so this file re-states fetchBundle's POSTURE
|
|
21
|
+
* rather than importing it. Named in the K1 PR body as the public-package
|
|
22
|
+
* boundary this card had to draw.
|
|
23
|
+
*
|
|
24
|
+
* Also deliberately skips fetchBundle's caller-side `checkLicense()` local
|
|
25
|
+
* JWT pre-check (install-skill.ts's step 2): that local verification lives
|
|
26
|
+
* in `@working-theory/license`, which for the same publish-boundary reason
|
|
27
|
+
* above cannot be a runtime dependency here. Instead the module distribution
|
|
28
|
+
* endpoint's own 401 response IS the license verdict — exactly the signal
|
|
29
|
+
* `fetchBundle` already surfaces via `isLicenseError`. This trades one local,
|
|
30
|
+
* no-network check for one authoritative, server-verified one; the caller-
|
|
31
|
+
* visible contract (present-but-invalid key ⟹ the honest "does not cover
|
|
32
|
+
* this" message) is unchanged.
|
|
33
|
+
*
|
|
34
|
+
* Returns the response body bytes on 200; throws a descriptive
|
|
35
|
+
* `FetchModuleError` on any failure path so the caller can map to an honest
|
|
36
|
+
* exit uniformly.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_AILK_MODULE_REGISTRY_URL = "https://ailaunchkit.ai/api/modules";
|
|
39
|
+
export interface FetchModuleOptions {
|
|
40
|
+
moduleName: string;
|
|
41
|
+
/** JWT used as `Authorization: Bearer <token>`. Must be non-empty. */
|
|
42
|
+
token: string;
|
|
43
|
+
/** Registry root; the module name is appended as a single path segment. */
|
|
44
|
+
registryUrl?: string;
|
|
45
|
+
/** Override for tests; defaults to `globalThis.fetch`. Never a live call in CI. */
|
|
46
|
+
fetchImpl?: typeof fetch;
|
|
47
|
+
}
|
|
48
|
+
export declare class FetchModuleError extends Error {
|
|
49
|
+
/** True iff the failure was a license-gating signal (HTTP 401). */
|
|
50
|
+
readonly isLicenseError: boolean;
|
|
51
|
+
constructor(message: string,
|
|
52
|
+
/** True iff the failure was a license-gating signal (HTTP 401). */
|
|
53
|
+
isLicenseError?: boolean);
|
|
54
|
+
}
|
|
55
|
+
export declare function fetchModule(options: FetchModuleOptions): Promise<Uint8Array>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated paid-module-bundle fetch (K1, #5089 — Flavor 3).
|
|
3
|
+
*
|
|
4
|
+
* Posture lifted verbatim from `packages/cli/src/lib/fetch-bundle.ts`
|
|
5
|
+
* (`fetchBundle`, the skill-registry twin of this function):
|
|
6
|
+
* - Bearer-JWT auth on every request.
|
|
7
|
+
* - `redirect: "error"` so a maliciously redirected response can't drop us
|
|
8
|
+
* onto an unintended host — the CLI must hit the configured module
|
|
9
|
+
* distribution only.
|
|
10
|
+
* - Empty-token guard before any network call (defense in depth — the
|
|
11
|
+
* caller should already have gated on an empty/absent key).
|
|
12
|
+
* - Status-code mapping: 401 → license invalid/revoked/lapsed, 404 →
|
|
13
|
+
* module content not yet available, other 4xx/5xx → upstream body.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately DOES NOT depend on `@working-theory/license` or
|
|
16
|
+
* `@working-theory/cli` — both are `publishConfig.access: "restricted"` and
|
|
17
|
+
* `create-ailk` is the one package in this monorepo published to PUBLIC npm
|
|
18
|
+
* (Apache-2.0, A1 disposed it `oss`). A runtime dependency on a restricted
|
|
19
|
+
* package would break `pnpm install` for every free-tier consumer, not just
|
|
20
|
+
* gate the paid modules — so this file re-states fetchBundle's POSTURE
|
|
21
|
+
* rather than importing it. Named in the K1 PR body as the public-package
|
|
22
|
+
* boundary this card had to draw.
|
|
23
|
+
*
|
|
24
|
+
* Also deliberately skips fetchBundle's caller-side `checkLicense()` local
|
|
25
|
+
* JWT pre-check (install-skill.ts's step 2): that local verification lives
|
|
26
|
+
* in `@working-theory/license`, which for the same publish-boundary reason
|
|
27
|
+
* above cannot be a runtime dependency here. Instead the module distribution
|
|
28
|
+
* endpoint's own 401 response IS the license verdict — exactly the signal
|
|
29
|
+
* `fetchBundle` already surfaces via `isLicenseError`. This trades one local,
|
|
30
|
+
* no-network check for one authoritative, server-verified one; the caller-
|
|
31
|
+
* visible contract (present-but-invalid key ⟹ the honest "does not cover
|
|
32
|
+
* this" message) is unchanged.
|
|
33
|
+
*
|
|
34
|
+
* Returns the response body bytes on 200; throws a descriptive
|
|
35
|
+
* `FetchModuleError` on any failure path so the caller can map to an honest
|
|
36
|
+
* exit uniformly.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_AILK_MODULE_REGISTRY_URL = "https://ailaunchkit.ai/api/modules";
|
|
39
|
+
export class FetchModuleError extends Error {
|
|
40
|
+
isLicenseError;
|
|
41
|
+
constructor(message,
|
|
42
|
+
/** True iff the failure was a license-gating signal (HTTP 401). */
|
|
43
|
+
isLicenseError = false) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.isLicenseError = isLicenseError;
|
|
46
|
+
this.name = "FetchModuleError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the fetch URL by joining `registryUrl` with `moduleName` as a single
|
|
51
|
+
* trailing path segment. `encodeURIComponent` on the module name is defense
|
|
52
|
+
* in depth — `parse-args.ts`'s `parseModules` already restricts the value to
|
|
53
|
+
* `RECOGNIZED_MODULES`, but a hostile value can never smuggle a path
|
|
54
|
+
* separator or query string here regardless.
|
|
55
|
+
*/
|
|
56
|
+
function buildUrl(registryUrl, moduleName) {
|
|
57
|
+
const trimmed = registryUrl.replace(/\/+$/, "");
|
|
58
|
+
return `${trimmed}/${encodeURIComponent(moduleName)}`;
|
|
59
|
+
}
|
|
60
|
+
export async function fetchModule(options) {
|
|
61
|
+
const { moduleName, token } = options;
|
|
62
|
+
if (token.length === 0) {
|
|
63
|
+
throw new FetchModuleError("internal error: fetchModule called without a token");
|
|
64
|
+
}
|
|
65
|
+
const registryUrl = options.registryUrl ??
|
|
66
|
+
process.env["AILK_MODULE_REGISTRY_URL"] ??
|
|
67
|
+
DEFAULT_AILK_MODULE_REGISTRY_URL;
|
|
68
|
+
const url = buildUrl(registryUrl, moduleName);
|
|
69
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
70
|
+
let response;
|
|
71
|
+
try {
|
|
72
|
+
response = await fetchImpl(url, {
|
|
73
|
+
method: "GET",
|
|
74
|
+
headers: {
|
|
75
|
+
Authorization: `Bearer ${token}`,
|
|
76
|
+
Accept: "application/gzip, application/octet-stream",
|
|
77
|
+
},
|
|
78
|
+
redirect: "error",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
83
|
+
throw new FetchModuleError(`module registry unreachable: ${detail}`);
|
|
84
|
+
}
|
|
85
|
+
if (response.status === 401) {
|
|
86
|
+
throw new FetchModuleError(`your licence does not cover the "${moduleName}" module (invalid, revoked, or lapsed)`,
|
|
87
|
+
/* isLicenseError */ true);
|
|
88
|
+
}
|
|
89
|
+
if (response.status === 404) {
|
|
90
|
+
throw new FetchModuleError(`"${moduleName}" module content not yet available — ping us at hello@ailaunchkit.ai`);
|
|
91
|
+
}
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const body = await safeReadBody(response);
|
|
94
|
+
throw new FetchModuleError(`module registry returned ${response.status} for "${moduleName}": ${body}`);
|
|
95
|
+
}
|
|
96
|
+
const buf = await response.arrayBuffer();
|
|
97
|
+
return new Uint8Array(buf);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Best-effort error body extraction. Tries JSON first (registry uses
|
|
101
|
+
* `{ error: "..." }` shapes), falls back to text, swallows any read failure
|
|
102
|
+
* so the caller's error message remains useful. Verbatim copy of
|
|
103
|
+
* `fetch-bundle.ts`'s helper of the same name.
|
|
104
|
+
*/
|
|
105
|
+
async function safeReadBody(response) {
|
|
106
|
+
try {
|
|
107
|
+
const text = await response.text();
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(text);
|
|
110
|
+
if (parsed !== null &&
|
|
111
|
+
typeof parsed === "object" &&
|
|
112
|
+
"error" in parsed &&
|
|
113
|
+
typeof parsed.error === "string") {
|
|
114
|
+
return parsed.error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* not JSON — fall through to raw text */
|
|
119
|
+
}
|
|
120
|
+
return text || response.statusText;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return response.statusText;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paid-module license gate (K1, #5089 — Flavor 3). Orchestrates
|
|
3
|
+
* `module-tier.ts`'s classification + `fetch-module.ts`'s authenticated
|
|
4
|
+
* fetch into the three honest states the CLI must surface:
|
|
5
|
+
*
|
|
6
|
+
* (a) no key + a paid module requested → conversion hint, non-zero, ZERO
|
|
7
|
+
* network (mirrors `install-skill.ts`'s free-tier short-circuit).
|
|
8
|
+
* (b) a lapsed/invalid key + a paid module → "your licence does not cover
|
|
9
|
+
* this", non-zero (the module distribution's own 401 — see
|
|
10
|
+
* `fetch-module.ts`'s file doc for why there is no local JWT
|
|
11
|
+
* pre-check here).
|
|
12
|
+
* (c) a valid key → every requested paid module's bundle bytes, ready for
|
|
13
|
+
* `extract-module-bundle.ts` to merge into the scaffold.
|
|
14
|
+
*
|
|
15
|
+
* A Free-only request (no paid modules) never reaches the key check at all —
|
|
16
|
+
* `resolvePaidModules` returns immediately, before touching `process.env` or
|
|
17
|
+
* the network, so a bare/all-Free scaffold is provably unaffected by this
|
|
18
|
+
* gate (K1's regression floor).
|
|
19
|
+
*/
|
|
20
|
+
import type { ModuleName } from "../parse-args.js";
|
|
21
|
+
export declare const CONVERSION_HINT_URL = "https://ailaunchkit.ai/pro";
|
|
22
|
+
/**
|
|
23
|
+
* Thrown for both honest-denial states (no key, and an invalid/lapsed key).
|
|
24
|
+
* The CLI does not distinguish them in its exit code — both are "this run
|
|
25
|
+
* cannot proceed without a paid module you don't have entitlement to" — but
|
|
26
|
+
* the MESSAGE always says which state it is, per the honest-state contract.
|
|
27
|
+
*/
|
|
28
|
+
export declare class LicenseRequiredError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
export interface ResolvePaidModulesOptions {
|
|
32
|
+
/** The requested module set's paid subset (see `module-tier.ts`). */
|
|
33
|
+
paidModules: ModuleName[];
|
|
34
|
+
/** Defaults to `process.env["AILK_LICENSE_KEY"]`. Injectable for tests. */
|
|
35
|
+
licenseKey?: string | undefined;
|
|
36
|
+
/** Forwarded to `fetchModule`; defaults to `AILK_MODULE_REGISTRY_URL` / the built-in default. */
|
|
37
|
+
registryUrl?: string;
|
|
38
|
+
/** Forwarded to `fetchModule`; defaults to `globalThis.fetch`. Never a live call in tests. */
|
|
39
|
+
fetchImpl?: typeof fetch;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve every requested paid module to its fetched bundle bytes, or throw
|
|
43
|
+
* `LicenseRequiredError` naming exactly why the run cannot proceed. Never
|
|
44
|
+
* partially resolves: on the first denial or failure, no further modules are
|
|
45
|
+
* fetched and the caller has written nothing to disk yet (the gate runs
|
|
46
|
+
* BEFORE the scaffold copy — see `programmatic.ts`'s `scaffold()`).
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolvePaidModules(options: ResolvePaidModulesOptions): Promise<Map<ModuleName, Uint8Array>>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { FetchModuleError, fetchModule } from "./fetch-module.js";
|
|
2
|
+
export const CONVERSION_HINT_URL = "https://ailaunchkit.ai/pro";
|
|
3
|
+
/**
|
|
4
|
+
* Thrown for both honest-denial states (no key, and an invalid/lapsed key).
|
|
5
|
+
* The CLI does not distinguish them in its exit code — both are "this run
|
|
6
|
+
* cannot proceed without a paid module you don't have entitlement to" — but
|
|
7
|
+
* the MESSAGE always says which state it is, per the honest-state contract.
|
|
8
|
+
*/
|
|
9
|
+
export class LicenseRequiredError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "LicenseRequiredError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve every requested paid module to its fetched bundle bytes, or throw
|
|
17
|
+
* `LicenseRequiredError` naming exactly why the run cannot proceed. Never
|
|
18
|
+
* partially resolves: on the first denial or failure, no further modules are
|
|
19
|
+
* fetched and the caller has written nothing to disk yet (the gate runs
|
|
20
|
+
* BEFORE the scaffold copy — see `programmatic.ts`'s `scaffold()`).
|
|
21
|
+
*/
|
|
22
|
+
export async function resolvePaidModules(options) {
|
|
23
|
+
const { paidModules } = options;
|
|
24
|
+
if (paidModules.length === 0) {
|
|
25
|
+
// Regression floor: zero key reads, zero network, for any request that
|
|
26
|
+
// names no paid module.
|
|
27
|
+
return new Map();
|
|
28
|
+
}
|
|
29
|
+
const token = options.licenseKey ?? process.env["AILK_LICENSE_KEY"];
|
|
30
|
+
if (token === undefined || token.length === 0) {
|
|
31
|
+
throw new LicenseRequiredError(conversionHint(paidModules));
|
|
32
|
+
}
|
|
33
|
+
const bundles = new Map();
|
|
34
|
+
for (const moduleName of paidModules) {
|
|
35
|
+
try {
|
|
36
|
+
const bytes = await fetchModule({
|
|
37
|
+
moduleName,
|
|
38
|
+
token,
|
|
39
|
+
...(options.registryUrl !== undefined
|
|
40
|
+
? { registryUrl: options.registryUrl }
|
|
41
|
+
: {}),
|
|
42
|
+
...(options.fetchImpl !== undefined
|
|
43
|
+
? { fetchImpl: options.fetchImpl }
|
|
44
|
+
: {}),
|
|
45
|
+
});
|
|
46
|
+
bundles.set(moduleName, bytes);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof FetchModuleError) {
|
|
50
|
+
if (err.isLicenseError) {
|
|
51
|
+
throw new LicenseRequiredError(`${err.message} → ${CONVERSION_HINT_URL}`);
|
|
52
|
+
}
|
|
53
|
+
// Not a license-gating signal (e.g. 404 "not yet available", or the
|
|
54
|
+
// registry unreachable) — a generic failure, not the honest-denial
|
|
55
|
+
// state. Re-thrown as-is; the CLI's top-level catch still turns this
|
|
56
|
+
// into a `[create-ailk] <message>` stderr write + non-zero exit.
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return bundles;
|
|
63
|
+
}
|
|
64
|
+
function conversionHint(paidModules) {
|
|
65
|
+
const list = paidModules.join(", ");
|
|
66
|
+
return `Pro module(s) requested (${list}) — set AILK_LICENSE_KEY first → ${CONVERSION_HINT_URL}`;
|
|
67
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free vs paid module classification (K1, #5089 — REL-015.3 "AI Launch Kit —
|
|
3
|
+
* Commercial line", D-17 "Flavor 3").
|
|
4
|
+
*
|
|
5
|
+
* The commercial line is decided: the paid modules do NOT ship on public npm
|
|
6
|
+
* — delivery itself is the gate. `create-ailk` scaffolds Free's modules from
|
|
7
|
+
* `templates/` with no key (today's path, unchanged); a paid module is
|
|
8
|
+
* FETCHED from the internal module distribution when `AILK_LICENSE_KEY`
|
|
9
|
+
* verifies (see `fetch-module.ts` + `module-license-gate.ts`).
|
|
10
|
+
*
|
|
11
|
+
* `FREE_MODULES` is the one hand-authored list (today just `marketing`, the
|
|
12
|
+
* always-available default). `PAID_MODULES` is derived from it — every other
|
|
13
|
+
* `RECOGNIZED_MODULES` entry — so adding a new module to the vocabulary
|
|
14
|
+
* (`parse-args.ts`) defaults it to "paid" until someone deliberately adds it
|
|
15
|
+
* to `FREE_MODULES`, rather than silently shipping it free.
|
|
16
|
+
*/
|
|
17
|
+
import { type ModuleName } from "../parse-args.js";
|
|
18
|
+
/** Modules scaffolded from `templates/` with no license — today's free path. */
|
|
19
|
+
export declare const FREE_MODULES: readonly ModuleName[];
|
|
20
|
+
/**
|
|
21
|
+
* Modules requiring a verified `AILK_LICENSE_KEY` — delivered via the paid
|
|
22
|
+
* module fetch path rather than bundled in the published `create-ailk` npm
|
|
23
|
+
* package. Derived (not hand-listed) so the free/paid split can never drift
|
|
24
|
+
* from `RECOGNIZED_MODULES` silently.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PAID_MODULES: readonly ModuleName[];
|
|
27
|
+
export interface ModuleClassification {
|
|
28
|
+
free: ModuleName[];
|
|
29
|
+
paid: ModuleName[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Split a requested (already-validated, `ModuleName`) module set into its
|
|
33
|
+
* free and paid subsets, each preserving the input's relative order.
|
|
34
|
+
*/
|
|
35
|
+
export declare function classifyModules(modules: ModuleName[]): ModuleClassification;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free vs paid module classification (K1, #5089 — REL-015.3 "AI Launch Kit —
|
|
3
|
+
* Commercial line", D-17 "Flavor 3").
|
|
4
|
+
*
|
|
5
|
+
* The commercial line is decided: the paid modules do NOT ship on public npm
|
|
6
|
+
* — delivery itself is the gate. `create-ailk` scaffolds Free's modules from
|
|
7
|
+
* `templates/` with no key (today's path, unchanged); a paid module is
|
|
8
|
+
* FETCHED from the internal module distribution when `AILK_LICENSE_KEY`
|
|
9
|
+
* verifies (see `fetch-module.ts` + `module-license-gate.ts`).
|
|
10
|
+
*
|
|
11
|
+
* `FREE_MODULES` is the one hand-authored list (today just `marketing`, the
|
|
12
|
+
* always-available default). `PAID_MODULES` is derived from it — every other
|
|
13
|
+
* `RECOGNIZED_MODULES` entry — so adding a new module to the vocabulary
|
|
14
|
+
* (`parse-args.ts`) defaults it to "paid" until someone deliberately adds it
|
|
15
|
+
* to `FREE_MODULES`, rather than silently shipping it free.
|
|
16
|
+
*/
|
|
17
|
+
import { RECOGNIZED_MODULES } from "../parse-args.js";
|
|
18
|
+
/** Modules scaffolded from `templates/` with no license — today's free path. */
|
|
19
|
+
export const FREE_MODULES = ["marketing"];
|
|
20
|
+
/**
|
|
21
|
+
* Modules requiring a verified `AILK_LICENSE_KEY` — delivered via the paid
|
|
22
|
+
* module fetch path rather than bundled in the published `create-ailk` npm
|
|
23
|
+
* package. Derived (not hand-listed) so the free/paid split can never drift
|
|
24
|
+
* from `RECOGNIZED_MODULES` silently.
|
|
25
|
+
*/
|
|
26
|
+
export const PAID_MODULES = RECOGNIZED_MODULES.filter((m) => !FREE_MODULES.includes(m));
|
|
27
|
+
/**
|
|
28
|
+
* Split a requested (already-validated, `ModuleName`) module set into its
|
|
29
|
+
* free and paid subsets, each preserving the input's relative order.
|
|
30
|
+
*/
|
|
31
|
+
export function classifyModules(modules) {
|
|
32
|
+
const paidSet = new Set(PAID_MODULES);
|
|
33
|
+
return {
|
|
34
|
+
free: modules.filter((m) => !paidSet.has(m)),
|
|
35
|
+
paid: modules.filter((m) => paidSet.has(m)),
|
|
36
|
+
};
|
|
37
|
+
}
|