create-ailk 0.7.0 → 0.8.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 +44 -10
- package/component-catalog.json +520 -5
- package/component-catalog.md +9 -6
- package/dist/cli.js +22 -24
- package/dist/lib/extract-module-bundle.js +5 -1
- package/dist/lib/module-license-gate.d.ts +79 -0
- package/dist/lib/module-license-gate.js +160 -12
- package/dist/lib/module-tier.d.ts +45 -16
- package/dist/lib/module-tier.js +76 -0
- package/dist/lib/pro-callout.js +10 -2
- package/dist/parse-args.d.ts +78 -10
- package/dist/parse-args.js +204 -14
- package/dist/programmatic.d.ts +39 -9
- package/dist/programmatic.js +163 -25
- package/dist/site-roster.js +11 -6
- package/dist/starter-grid.d.ts +216 -0
- package/dist/starter-grid.js +449 -0
- package/dist/surfaces.d.ts +4 -4
- package/dist/surfaces.js +102 -43
- package/dist/template-manifest.d.ts +36 -17
- package/dist/template-manifest.js +121 -367
- package/dist/template-names.d.ts +14 -12
- package/dist/template-names.js +13 -17
- package/package.json +3 -3
- package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -1
- package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
- package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +0 -1
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +0 -1
- package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
- package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +15 -0
- package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
- package/site-templates/designs/standard/DESIGN.md +5 -0
- package/site-templates/marketing-site/DESIGN.md +5 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +89 -25
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +35 -14
- package/site-templates/marketing-site-free/DESIGN.md +5 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +95 -29
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +74 -44
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +17 -29
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -1
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
- package/site-templates/saas-product/DESIGN.md +5 -0
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
- package/site-templates/{simple-service → shapes/local-business-service}/TEMPLATE-MANIFEST.json +96 -76
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -0
- package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
- package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
- package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/{simple-service → shapes/local-business-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +7 -8
- package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
- package/site-templates/shapes/local-business-service/provenance.json +4 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +362 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -0
- package/site-templates/shapes/organization-service/ailk-template.json +10 -0
- package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/shapes/organization-service/provenance.json +4 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -0
- package/site-templates/shapes/person-service/ailk-template.json +10 -0
- package/site-templates/shapes/person-service/content/_site.mdx +85 -0
- package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
- package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
- package/site-templates/shapes/person-service/provenance.json +4 -0
- package/templates/.claude/skills/README.md +3 -2
- package/templates/.claude/skills/add-page/references/page-types.md +4 -0
- package/templates/.claude/skills/setup-database/SKILL.md +156 -0
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
- package/templates/.claude/skills/setup-deploy/SKILL.md +64 -32
- package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +19 -28
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +58 -102
- package/templates/DESIGN.md +5 -0
- package/templates/README.md +46 -38
- package/templates/apps/api/CLAUDE.md +3 -2
- package/templates/apps/api/src/config/__tests__/index.test.ts +115 -14
- package/templates/apps/api/src/config/index.ts +35 -16
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
- package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
- package/templates/apps/api/src/openapi/spec.ts +63 -12
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
- package/templates/apps/api/src/routes/leads/index.ts +46 -1
- package/templates/apps/api/src/server.ts +7 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
- package/templates/apps/api/src/services/lead-routing.ts +140 -30
- package/templates/apps/api/src/services/leads-inbox.ts +48 -11
- package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
- package/templates/apps/mcp/src/tools/create_project.ts +21 -6
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +14 -6
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +10 -9
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +227 -84
- package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
- package/templates/apps/web/app/[locale]/contact/page.tsx +105 -15
- package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
- package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/layout.tsx +37 -5
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +106 -14
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +18 -3
- package/templates/apps/web/lib/nav-config.ts +48 -36
- package/templates/apps/web/lib/site-identity.ts +14 -0
- package/templates/apps/web/messages/en.json +28 -2
- package/templates/content/_site.mdx +4 -2
- package/templates/content/en/BlogList/blog.mdx +37 -16
- package/templates/content/en/ContactPage/contact.mdx +14 -44
- package/templates/content/en/HomePage/home.mdx +8 -16
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
- package/templates/content/en/UseCasesList/use-cases.mdx +6 -6
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/versions.json +33 -33
- package/site-templates/_base/content/en/UseCase/first-use-case.mdx +0 -23
- package/site-templates/_base/content/en/UseCase/second-use-case.mdx +0 -23
- package/site-templates/_base/content/en/UseCase/third-use-case.mdx +0 -23
- package/site-templates/simple-service/DESIGN.md +0 -162
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -333
- package/site-templates/simple-service/ailk-template.json +0 -11
- package/site-templates/simple-service/content/en/BookCall/book-call.mdx +0 -53
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -32
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
- package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
- package/site-templates/simple-service/provenance.json +0 -4
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -211
- package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +0 -96
- package/templates/content/en/SoftwareProduct/product.mdx +0 -175
package/component-catalog.md
CHANGED
|
@@ -15,13 +15,13 @@ The component inventory of `@working-theory/ui` and the shells of `@working-theo
|
|
|
15
15
|
3. Check the Primitives, Panels and Shells tables too — a low-level control, a collapsible layout container, or a route-level frame lives there, not under Sections or Blocks.
|
|
16
16
|
4. Only then build. If you do build, you are declaring the capability is genuinely absent.
|
|
17
17
|
|
|
18
|
-
Inventory: **
|
|
18
|
+
Inventory: **303 component files** across 16 tiers — 55 sections, 99 blocks, 18 chrome, 79 primitives, 6 panels, 5 shells, 5 docs-blog-blocks, 1 docs-blog-chrome, 2 docs-blog-shells, 2 scheduling-sections, 5 scheduling-blocks, 3 signal-collectors-sections, 8 signal-collectors-blocks, 2 commerce-sections, 12 commerce-blocks, 1 commerce-panels. 300 distinct names: `SchedulerEmbed`, `Separator`, `Tabs` exist in more than one tier as genuinely different components, listed once per tier with its own source path.
|
|
19
19
|
|
|
20
|
-
Description coverage: **
|
|
20
|
+
Description coverage: **299/303** components carry a capability description derived from source — 44 from the section registry's hand-authored when-to-use text, 224 from the component's own doc-comment header, 31 from its tier barrel's roster line. **4** have none; they are listed under [Undescribed components](#undescribed-components) rather than given a sentence invented from their name.
|
|
21
21
|
|
|
22
|
-
Of those, **
|
|
22
|
+
Of those, **268** are *strong* — a capability sentence you could match a requirement against — and **31** are *weak*: either one short clause, or a long one that spends itself on wiring (client boundaries, class names, which primitives it composes) instead of on what the thing is for. Weak rows are marked `(weak)` and listed in full under [Weak descriptions](#weak-descriptions). Coverage is 99%, but **findable-without-luck coverage is 88%**.
|
|
23
23
|
|
|
24
|
-
Per tier, strong out of total: **Sections**
|
|
24
|
+
Per tier, strong out of total: **Sections** 54/55 · **Blocks** 93/99 · **Chrome** 10/18 · **Primitives** 64/79 (4 undescribed) · **Panels** 5/6 · **Shells** 4/5 · **Docs + Blog Blocks** 5/5 · **Docs + Blog Chrome** 1/1 · **Docs + Blog Shells** 0/2 · **Scheduling Sections** 2/2 · **Scheduling Blocks** 5/5 · **Signal-Collectors Sections** 3/3 · **Signal-Collectors Blocks** 7/8 · **Commerce Sections** 2/2 · **Commerce Blocks** 12/12 · **Commerce Panels** 1/1. A tier low on this line is a tier where a capability search is most likely to come up empty on something that exists.
|
|
25
25
|
|
|
26
26
|
That grade is **mechanical**. It counts what it can measure — how much a description adds beyond the component's own name, how much of it is spent on wiring, whether the extraction came out damaged — and it cannot tell a fluent paragraph about the wrong subject from a useful one. A person reading these tables will find more rows unusable than the weak count admits. Treat it as a floor on the problem, not a measurement of it, and read the source before concluding a capability is absent.
|
|
27
27
|
|
|
@@ -53,7 +53,7 @@ The left column is search vocabulary — how a person phrases the need. It is ha
|
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
56
|
-
## Sections (
|
|
56
|
+
## Sections (55)
|
|
57
57
|
|
|
58
58
|
Page-level content bands. Most are MDX-driven (authored by `type` in page frontmatter and dispatched by `SectionRenderer`); a few are composed directly in a route.
|
|
59
59
|
|
|
@@ -66,6 +66,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
|
|
|
66
66
|
| `AskAi` | Use above the footer — hands the visitor to a third-party AI seeded with the entity question. Bounded to five measured providers (ChatGPT/Claude/Google AI/Grok/Perplexity), each deep-linked with an authored prompt. The `ticker` variant renders a compact full-width band instead of the default headline/description/provider-row layout. | MDX `type: ask-ai` |
|
|
67
67
|
| `AuthPanel` | A sign-in/sign-up panel with an optional bounded OAuth provider row (google, github) — UI-contract only, no auth backend; submit and provider clicks are callback props the composing page wires up. | MDX `type: auth-panel` · `import { AuthPanel } from "@working-theory/ui/sections"` |
|
|
68
68
|
| `Awards` | A two-column evidence band — headline/CTA left, up to 5 award or press badges right — use for third-party recognition (badges may render as labeled placeholders until real marks are supplied). | MDX `type: awards` |
|
|
69
|
+
| `BeforeAfter` | A before/after comparison — use on a case-study or portfolio page to show what a client had and what they have now: two labeled panels behind a Side-by-side / Before / After selector, each body an image or rows of content, with an optional catalyst column between them and a key-takeaway box below. | MDX `type: before-after` · `import { BeforeAfter } from "@working-theory/ui/sections"` |
|
|
69
70
|
| `BentoGrid` | A mixed-span mosaic of feature cards — use for a visually varied capability overview. | MDX `type: bento-grid` · `import { BentoGrid } from "@working-theory/ui/sections"` |
|
|
70
71
|
| `BlogList` | A blog post list with pagination — use for the blog index page only. | MDX `type: blog-list` · `import { BlogList } from "@working-theory/ui/sections"` |
|
|
71
72
|
| `BlogPost` | A single blog post body with TOC and related posts — use only on the blog-post chassis. | MDX `type: blog-post` · `import { BlogPost } from "@working-theory/ui/sections"` |
|
|
@@ -118,7 +119,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
|
|
|
118
119
|
|
|
119
120
|
---
|
|
120
121
|
|
|
121
|
-
## Blocks (
|
|
122
|
+
## Blocks (99)
|
|
122
123
|
|
|
123
124
|
Intermediate composition tiles — the pieces sections are built from. Compose primitives and panels; never import a section.
|
|
124
125
|
|
|
@@ -150,6 +151,7 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
|
|
|
150
151
|
| `ConsentSettingsButton` | Footer "Cookie settings" control. Lets a visitor who already accepted/declined change their mind later, from the footer, without reloading the page. Lives at the BLOCKS tier (not chrome) so `FooterBottomBar` (a block) can render it without an upward import; `BannerConsentIsland` (chrome) imports the exported event constant DOWNWARD, per the component tier hierarchy. | internal — compose from `packages/ui/src/blocks/ConsentSettingsButton.tsx` |
|
|
151
152
|
| `ConsentStatement` | The sentence a person agrees to, with its policy links inline. | `import { ConsentStatement } from "@working-theory/ui/blocks"` |
|
|
152
153
|
| `ContactChannelItem` | Renders one support channel entry: colored icon square + headline + description paragraph + CTA link (with trailing arrow icon). | `import { ContactChannelItem } from "@working-theory/ui/blocks"` |
|
|
154
|
+
| `ContactFallback` | The contact email in place of a contact form. Shown when a site cannot take messages through its form: in production with neither a database nor an email key, the page renders this instead, and `LeadCaptureForm`'s `unavailable` state renders this same markup when a submit is refused — so the two fallbacks are one component. | `import { ContactFallback } from "@working-theory/ui/blocks"` |
|
|
153
155
|
| `ContactFormFields` | Renders the form field grid (Input / Textarea / Select primitives) + submit Button + optional consent text footer. | `import { ContactFormFields } from "@working-theory/ui/blocks"` |
|
|
154
156
|
| `ContactInfoAside` | For ContactForm 'split-with-pattern' variant. Renders address / phone / email contact info rows. Each row: sr-only <dt> label + visible <dd> with Icon + Text. Phone and email render as tel:/mailto: links (click-to-call / click-to-email). | `import { ContactInfoAside } from "@working-theory/ui/blocks"` |
|
|
155
157
|
| `ConversationalIntake` | The UI half of the conversational-intake feature: renders the transcript, closed-question answer pills, a free-text form, a thinking indicator, and Skip — driven entirely through the `NextTurn` interface exported by `./engine` (merged). | `import { ConversationalIntake } from "@working-theory/ui/blocks"` |
|
|
@@ -167,6 +169,7 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
|
|
|
167
169
|
| `FormCard` | Stateless, centered max-w-md form-card chassis: a Card wrapping an optional title + description header, a field-stack body (children), and a footer slot for the primary action + a secondary line below it. The SHARED look composed by both the contact-form section's 'card' variant and the auth-panel section — this block owns none of their field/state machinery, only the surrounding chrome. | `import { FormCard } from "@working-theory/ui/blocks"` |
|
|
168
170
|
| `InviteMemberDialog` | The invite modal — reimplements the ploy.ai "Invite Team Member" capture (Email textbox + Role select, default Member, options Member/Admin, helper "Admins can invite and remove members." + Send Invitation). | `import { InviteMemberDialog } from "@working-theory/ui/blocks"` |
|
|
169
171
|
| `KeyPointsPanel` | ONE shared block composed by two consumers: Hero's `panel` variant (text-left / panel-right) and Pricing's `band-hero` arrangement (header-left / panel-right, tiers below). | internal — compose from `packages/ui/src/blocks/KeyPointsPanel.tsx` |
|
|
172
|
+
| `LeadCaptureForm` | The one contact form every template renders. Submits ONLY through the `action` prop — a Server Action the page binds — so there is no fetch, no URL and no browser-side JSON here. | `import { LeadCaptureForm } from "@working-theory/ui/blocks"` |
|
|
170
173
|
| `LinkCard` | A whole-tile link card: the entire tile IS the anchor (Claude-docs link-card anatomy). Optional leading icon, a title heading, and an optional description paragraph. Hover fill + chrome are intrinsic to the block — never props. | `import { LinkCard } from "@working-theory/ui/blocks"` |
|
|
171
174
|
| `LinkListItem` | Hub-page spoke list row: prose Link + optional description, enlarged to the 44px touch-target floor. Fix: the enlarged-hit-area pattern (same idiom as ShortcutTrigger, SegmentedControl's touchTargetFloor) — `min-h-(control-height-big)` clamps the anchor's clickable box to 44px while `inline-flex items-center` keeps the visible text at its normal prose size. | `import { LinkListItem } from "@working-theory/ui/blocks"` |
|
|
172
175
|
| `ListRow` | The PHONE form of one admin list item. The reference is `apps-paid/admin/design/demos/first-steps/demo.html:475` (`rowDoc`), which lays the row out as: a leading type glyph, a two-line text column with the MUTED META LINE FIRST and the item's name second (every phone screen in the demo agrees), an optional trailing control, and a chevron. | `import { ListRow } from "@working-theory/ui/blocks"` |
|
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ import { fileURLToPath } from "node:url";
|
|
|
37
37
|
import { ADD_MODULE_HELP, AddModuleRefusedError, formatAddModuleReport, parseAddModuleArgs, } from "./add-module.js";
|
|
38
38
|
import { FREE_PREREQUISITES_NOTE, LICENSED_PREREQUISITES_NOTE, isLicensedInstall, proCallout, } from "./lib/pro-callout.js";
|
|
39
39
|
import { LICENSE_SET_HELP, runLicenseSet } from "./license-set.js";
|
|
40
|
-
import { helpText, parseCliArgs, parseModules } from "./parse-args.js";
|
|
40
|
+
import { helpText, parseCliArgs, parseModules, resolveCliShape, toScaffoldOptions, } from "./parse-args.js";
|
|
41
41
|
import { addModule, formatSurfaceDrift, scaffold, syncArchitectureSync, syncCatalogSync, syncRoutesSync, } from "./programmatic.js";
|
|
42
42
|
import { CORE_SURFACE_PATHS, SURFACE_ALIASES } from "./surfaces.js";
|
|
43
43
|
import { SYNC_ROUTES_HELP, formatSyncRoutesReport, parseSyncRoutesArgs, } from "./sync-routes.js";
|
|
@@ -141,7 +141,9 @@ async function main() {
|
|
|
141
141
|
readStdin: () => readFileSync(0, "utf8"),
|
|
142
142
|
}));
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
// No silent default (#6754 D2/D3): a scaffold run naming neither --shape
|
|
145
|
+
// nor --template asks for a shape on a terminal and is refused without one.
|
|
146
|
+
const opts = await resolveCliShape(parseCliArgs(argv, { interactive: process.stdin.isTTY === true }), { input: process.stdin, output: process.stdout });
|
|
145
147
|
// Deprecation notice for a retired `--surfaces` id (#5563 D2). `surfaces.ts`
|
|
146
148
|
// stays pure — `canonicalizeSurface` only resolves the alias — so this is
|
|
147
149
|
// the one place that prints: compare the raw `--surfaces` argv token
|
|
@@ -209,23 +211,16 @@ async function main() {
|
|
|
209
211
|
// scaffold() gates + fetches any requested paid module (K1, #5089) and any
|
|
210
212
|
// named site template's own bundle (#5481), then validates and copies the
|
|
211
213
|
// local template — throws ValidationError or LicenseRequiredError on
|
|
212
|
-
// rejection (caught at top level below).
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
214
|
+
// rejection (caught at top level below). The network invariant (#6759 D6):
|
|
215
|
+
// no key → no request at all; a key with no --modules → the D4 level probe
|
|
216
|
+
// (3-4 requests); an explicit --modules list → requests only for the paid
|
|
217
|
+
// modules it names, exactly as before this card. `--template
|
|
218
|
+
// marketing-site-free` still rides the no-key case (record D11): its
|
|
216
219
|
// `requires` is all-free, so it installs from the shipped
|
|
217
220
|
// `site-templates/` tree with no key and no request.
|
|
218
|
-
const { targetDir: targetAbs, surfaces } = await scaffold(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
surfaces: opts.surfaces,
|
|
222
|
-
withCatalog: opts.withCatalog,
|
|
223
|
-
// Spread rather than passed as `undefined` so a run naming no template
|
|
224
|
-
// hands `scaffold()` exactly the option object it got before the flag
|
|
225
|
-
// existed (spec 5481 § 3 DD3's byte-identity floor).
|
|
226
|
-
...(opts.template !== undefined ? { template: opts.template } : {}),
|
|
227
|
-
...(opts.license !== undefined ? { license: opts.license } : {}),
|
|
228
|
-
});
|
|
221
|
+
const { targetDir: targetAbs, surfaces, starter, modules: effectiveModules, } = await scaffold(toScaffoldOptions(opts));
|
|
222
|
+
// The starter scaffold() resolved — from --template, or from --shape (#6754 D4).
|
|
223
|
+
const templateName = starter?.template ?? opts.template;
|
|
229
224
|
if (opts.install) {
|
|
230
225
|
// Hard-coded argv. User input only flows through `cwd`, never the command.
|
|
231
226
|
const result = spawnSync("pnpm", ["install"], {
|
|
@@ -238,16 +233,19 @@ async function main() {
|
|
|
238
233
|
}
|
|
239
234
|
}
|
|
240
235
|
// Next-steps message. A licensed install (#6480) is not told it needs no
|
|
241
|
-
// licence key, and is not pitched the Pro tier it already owns.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
});
|
|
236
|
+
// licence key, and is not pitched the Pro tier it already owns. Reads the
|
|
237
|
+
// EFFECTIVE module set (#6759 D6) — result.modules already carries any
|
|
238
|
+
// template's requires and the key's derived level, so it stays correct for
|
|
239
|
+
// a keyed default scaffold, which opts.modules alone would not have shown.
|
|
240
|
+
const licensed = isLicensedInstall({ modules: effectiveModules });
|
|
246
241
|
const lines = [
|
|
247
242
|
"",
|
|
248
|
-
|
|
249
|
-
? `Scaffolded AI Launch Kit starter from the "${
|
|
243
|
+
templateName !== undefined
|
|
244
|
+
? `Scaffolded AI Launch Kit starter from the "${templateName}" template.`
|
|
250
245
|
: "Scaffolded AI Launch Kit starter.",
|
|
246
|
+
...(starter !== undefined
|
|
247
|
+
? [`Starter: shape ${starter.shape}, design ${starter.design}.`]
|
|
248
|
+
: []),
|
|
251
249
|
"",
|
|
252
250
|
];
|
|
253
251
|
// Prerequisites (#6440 AC4) — every one the scaffold enforces, and nothing
|
|
@@ -655,6 +655,11 @@ export function installBundleTree(options) {
|
|
|
655
655
|
* path-safety check is needed here — `srcRoot` is our own staging tree, not
|
|
656
656
|
* attacker-controlled input.
|
|
657
657
|
*
|
|
658
|
+
* A directory is created only as the parent of a file actually copied, never
|
|
659
|
+
* for its own sake: a source directory whose every file is excluded (mapped
|
|
660
|
+
* away) leaves nothing behind at its original path (#6787 — an empty root
|
|
661
|
+
* `content/en/blog/` reads as an unregistered page type).
|
|
662
|
+
*
|
|
658
663
|
* `markers`, when given, is substituted into every TEXT file on the way
|
|
659
664
|
* through (#6460) — see `InstallBundleTreeOptions.markers`.
|
|
660
665
|
*/
|
|
@@ -669,7 +674,6 @@ function copyTree(srcRoot, destRoot, excluded, markers, policy, report) {
|
|
|
669
674
|
const rel = relative(srcRoot, srcPath);
|
|
670
675
|
const destPath = join(destRoot, rel);
|
|
671
676
|
if (entry.isDirectory()) {
|
|
672
|
-
mkdirSync(destPath, { recursive: true });
|
|
673
677
|
stack.push(srcPath);
|
|
674
678
|
}
|
|
675
679
|
else if (entry.isFile()) {
|
|
@@ -16,8 +16,14 @@
|
|
|
16
16
|
* `resolvePaidModules` returns immediately, before touching `process.env` or
|
|
17
17
|
* the network, so a bare/all-Free scaffold is provably unaffected by this
|
|
18
18
|
* gate (K1's regression floor).
|
|
19
|
+
*
|
|
20
|
+
* Also carries the design gate (#6760): `assertDesignAllowed` refuses a
|
|
21
|
+
* non-`standard` design at the `free` level, naming the design and the
|
|
22
|
+
* run's level, before `scaffold()`'s first disk write.
|
|
19
23
|
*/
|
|
20
24
|
import type { ModuleName } from "../parse-args.js";
|
|
25
|
+
import type { StarterDesign } from "../template-manifest.js";
|
|
26
|
+
import { type Level } from "./module-tier.js";
|
|
21
27
|
/**
|
|
22
28
|
* Where a refused run sends the buyer. `/pricing`, not `/pro`: there is no
|
|
23
29
|
* `/pro` route and there will not be one (#6439, operator decision OD-2) —
|
|
@@ -33,6 +39,18 @@ export declare const CONVERSION_HINT_URL = "https://ailaunchkit.ai/pricing";
|
|
|
33
39
|
export declare class LicenseRequiredError extends Error {
|
|
34
40
|
constructor(message: string);
|
|
35
41
|
}
|
|
42
|
+
/** Designs every level renders (#6760 D1). Anything else in `STARTER_DESIGNS` is Pro and up. */
|
|
43
|
+
export declare const FREE_DESIGNS: readonly StarterDesign[];
|
|
44
|
+
/** Levels that render any design (#6760 D1) — an allow-list: an unknown level is not in it. */
|
|
45
|
+
export declare const DESIGN_LEVELS: readonly Level[];
|
|
46
|
+
/**
|
|
47
|
+
* Refuse a non-`standard` design at a level below Pro (#6760 D1, D6). Pure
|
|
48
|
+
* and synchronous — takes the level `deriveLevel`/`resolveModuleSet` already
|
|
49
|
+
* resolved; never reads a key or derives a level itself (D2, fail-closed:
|
|
50
|
+
* both checks below are allow-lists, so an unrecognized `level` refuses
|
|
51
|
+
* rather than granting it).
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertDesignAllowed(design: StarterDesign | undefined, level: Level): void;
|
|
36
54
|
export interface ResolvePaidModulesOptions {
|
|
37
55
|
/** The requested module set's paid subset (see `module-tier.ts`). */
|
|
38
56
|
paidModules: ModuleName[];
|
|
@@ -52,7 +70,20 @@ export interface ResolvePaidModulesOptions {
|
|
|
52
70
|
* to do. Absent ⟹ a plain `--modules` run, whose messages are unchanged.
|
|
53
71
|
*/
|
|
54
72
|
templateName?: string;
|
|
73
|
+
/**
|
|
74
|
+
* The key's derived level (#6759 D5), when one was derived on this call's
|
|
75
|
+
* path. Absent on the `--modules`-keep branch, where no level is derived
|
|
76
|
+
* (NFR-2) — the refusal then names only the module and its own level,
|
|
77
|
+
* never the caller's.
|
|
78
|
+
*/
|
|
79
|
+
level?: Level;
|
|
55
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The message phrase for one module (D5): what level includes it, or that it
|
|
83
|
+
* is an add-on. Pure — derived from `levelOf`, never from the current run's
|
|
84
|
+
* own derived level.
|
|
85
|
+
*/
|
|
86
|
+
export declare function levelPhrase(m: ModuleName): string;
|
|
56
87
|
/**
|
|
57
88
|
* Resolve every requested paid module to its fetched bundle bytes, or throw
|
|
58
89
|
* `LicenseRequiredError` naming exactly why the run cannot proceed. Never
|
|
@@ -61,3 +92,51 @@ export interface ResolvePaidModulesOptions {
|
|
|
61
92
|
* BEFORE the scaffold copy — see `programmatic.ts`'s `scaffold()`).
|
|
62
93
|
*/
|
|
63
94
|
export declare function resolvePaidModules(options: ResolvePaidModulesOptions): Promise<Map<ModuleName, Uint8Array>>;
|
|
95
|
+
/** Inputs shared by `deriveLevel` and `resolveModuleSet` (D4/D5). */
|
|
96
|
+
export interface GateOptions {
|
|
97
|
+
/** Defaults to `process.env["AILK_LICENSE_KEY"]`. Injectable for tests. */
|
|
98
|
+
licenseKey?: string | undefined;
|
|
99
|
+
/** Forwarded to `fetchModule`; defaults to `AILK_MODULE_REGISTRY_URL` / the built-in default. */
|
|
100
|
+
registryUrl?: string;
|
|
101
|
+
/** Forwarded to `fetchModule`; defaults to this package's own version. */
|
|
102
|
+
version?: string;
|
|
103
|
+
/** Forwarded to `fetchModule`; defaults to `globalThis.fetch`. Never a live call in tests. */
|
|
104
|
+
fetchImpl?: typeof fetch;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Derive the key's level from what the registry actually answers (D4) — the
|
|
108
|
+
* level only chooses which modules to ASK FOR; every byte is still
|
|
109
|
+
* authorized by the registry per module. Nothing client-side is an
|
|
110
|
+
* entitlement claim: the JWT is never parsed here.
|
|
111
|
+
*
|
|
112
|
+
* No key ⟹ `"free"`, zero network. A present key is shape-checked first,
|
|
113
|
+
* whichever source it came from (SEC-2). Then: probe every paid member of
|
|
114
|
+
* `LEVEL_MODULES.pro`, in order — a licence error on any of them is refused
|
|
115
|
+
* (a key that does not unlock all of Pro is not silently downgraded); any
|
|
116
|
+
* other failure (404, 5xx, unreachable) is wrapped so the caller sees it
|
|
117
|
+
* could not determine the level, and is told the escape hatch (`--modules`).
|
|
118
|
+
* Then probe `multi-tenant` (what Pro Plus adds beyond Pro): a licence error
|
|
119
|
+
* there is the expected "this key is Pro, not Pro Plus" answer, not a
|
|
120
|
+
* refusal.
|
|
121
|
+
*/
|
|
122
|
+
export declare function deriveLevel(options: GateOptions): Promise<{
|
|
123
|
+
level: Level;
|
|
124
|
+
bundles: Map<ModuleName, Uint8Array>;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* The one entry point that turns `--modules` + the key into the effective
|
|
128
|
+
* module set and its resolved bundles (D5) — `programmatic.ts`'s `scaffold()`
|
|
129
|
+
* calls this for every run except a named `--template` alias (NFR-4).
|
|
130
|
+
*
|
|
131
|
+
* A named list with a non-addition (the KEEP branch, D2) resolves it exactly
|
|
132
|
+
* as today, with no level derived and no extra request (NFR-2). Otherwise the
|
|
133
|
+
* level is derived first, the set composed from it, and any named addition
|
|
134
|
+
* not already fetched during the level probe is resolved on top.
|
|
135
|
+
*/
|
|
136
|
+
export declare function resolveModuleSet(options: GateOptions & {
|
|
137
|
+
named?: readonly ModuleName[] | undefined;
|
|
138
|
+
}): Promise<{
|
|
139
|
+
modules: ModuleName[];
|
|
140
|
+
bundles: Map<ModuleName, Uint8Array>;
|
|
141
|
+
level?: Level;
|
|
142
|
+
}>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { FetchModuleError, fetchModule } from "./fetch-module.js";
|
|
2
|
+
import { assertLicenseKeyShape } from "./license-key-files.js";
|
|
3
|
+
import { ADD_ONS, LEVEL_LABEL, LEVEL_MODULES, OPT_IN_MODULES, PAID_MODULES, classifyModules, composeModuleSet, levelOf, } from "./module-tier.js";
|
|
2
4
|
/**
|
|
3
5
|
* Where a refused run sends the buyer. `/pricing`, not `/pro`: there is no
|
|
4
6
|
* `/pro` route and there will not be one (#6439, operator decision OD-2) —
|
|
@@ -17,6 +19,53 @@ export class LicenseRequiredError extends Error {
|
|
|
17
19
|
this.name = "LicenseRequiredError";
|
|
18
20
|
}
|
|
19
21
|
}
|
|
22
|
+
/** Designs every level renders (#6760 D1). Anything else in `STARTER_DESIGNS` is Pro and up. */
|
|
23
|
+
export const FREE_DESIGNS = ["standard"];
|
|
24
|
+
/** Levels that render any design (#6760 D1) — an allow-list: an unknown level is not in it. */
|
|
25
|
+
export const DESIGN_LEVELS = ["pro", "pro-plus"];
|
|
26
|
+
/**
|
|
27
|
+
* Refuse a non-`standard` design at a level below Pro (#6760 D1, D6). Pure
|
|
28
|
+
* and synchronous — takes the level `deriveLevel`/`resolveModuleSet` already
|
|
29
|
+
* resolved; never reads a key or derives a level itself (D2, fail-closed:
|
|
30
|
+
* both checks below are allow-lists, so an unrecognized `level` refuses
|
|
31
|
+
* rather than granting it).
|
|
32
|
+
*/
|
|
33
|
+
export function assertDesignAllowed(design, level) {
|
|
34
|
+
if (design === undefined || FREE_DESIGNS.includes(design))
|
|
35
|
+
return;
|
|
36
|
+
if (DESIGN_LEVELS.includes(level))
|
|
37
|
+
return;
|
|
38
|
+
throw new LicenseRequiredError(`design "${design}" is a Pro feature — this run's licence level is ${LEVEL_LABEL[level]}; pass --license <key> or set AILK_LICENSE_KEY with a Pro or Pro Plus key → ${CONVERSION_HINT_URL}`);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The message phrase for one module (D5): what level includes it, or that it
|
|
42
|
+
* is an add-on. Pure — derived from `levelOf`, never from the current run's
|
|
43
|
+
* own derived level.
|
|
44
|
+
*/
|
|
45
|
+
export function levelPhrase(m) {
|
|
46
|
+
const level = levelOf(m);
|
|
47
|
+
if (level === "add-on") {
|
|
48
|
+
return `"${m}" is an add-on, bought on top of Pro or Pro Plus`;
|
|
49
|
+
}
|
|
50
|
+
if (level === "free") {
|
|
51
|
+
return `"${m}" is included at ${LEVEL_LABEL.free}`;
|
|
52
|
+
}
|
|
53
|
+
return `"${m}" is included at ${LEVEL_LABEL[level]}`;
|
|
54
|
+
}
|
|
55
|
+
/** M1 / M1t (D5): the no-key refusal, with or without a named template. */
|
|
56
|
+
function noKeyMessage(paidModules, templateName) {
|
|
57
|
+
const phrases = paidModules.map((m) => levelPhrase(m)).join("; ");
|
|
58
|
+
const intro = templateName !== undefined
|
|
59
|
+
? `template "${templateName}" needs a licence: ${phrases}`
|
|
60
|
+
: `no licence key, so this is a Free scaffold: ${phrases}`;
|
|
61
|
+
return `${intro} — set AILK_LICENSE_KEY first → ${CONVERSION_HINT_URL}`;
|
|
62
|
+
}
|
|
63
|
+
/** M2 (D5): a key the registry rejected for one module. */
|
|
64
|
+
function licenseErrorMessage(err, moduleName, templateName, level) {
|
|
65
|
+
const templateSuffix = templateName !== undefined ? ` (required by template "${templateName}")` : "";
|
|
66
|
+
const levelSuffix = level !== undefined ? ` — your licence is at ${LEVEL_LABEL[level]}` : "";
|
|
67
|
+
return `${err.message}; ${levelPhrase(moduleName)}${templateSuffix}${levelSuffix} → ${CONVERSION_HINT_URL}`;
|
|
68
|
+
}
|
|
20
69
|
/**
|
|
21
70
|
* Resolve every requested paid module to its fetched bundle bytes, or throw
|
|
22
71
|
* `LicenseRequiredError` naming exactly why the run cannot proceed. Never
|
|
@@ -33,8 +82,11 @@ export async function resolvePaidModules(options) {
|
|
|
33
82
|
}
|
|
34
83
|
const token = options.licenseKey ?? process.env["AILK_LICENSE_KEY"];
|
|
35
84
|
if (token === undefined || token.length === 0) {
|
|
36
|
-
throw new LicenseRequiredError(
|
|
85
|
+
throw new LicenseRequiredError(noKeyMessage(paidModules, options.templateName));
|
|
37
86
|
}
|
|
87
|
+
// SEC-2 — the `--license` flag and the env var are validated alike,
|
|
88
|
+
// whichever source this token came from, before it reaches a request.
|
|
89
|
+
assertLicenseKeyShape(token);
|
|
38
90
|
const bundles = new Map();
|
|
39
91
|
for (const moduleName of paidModules) {
|
|
40
92
|
try {
|
|
@@ -55,11 +107,9 @@ export async function resolvePaidModules(options) {
|
|
|
55
107
|
if (err instanceof FetchModuleError) {
|
|
56
108
|
if (err.isLicenseError) {
|
|
57
109
|
// DD5 row 2: the mapped 401/403 already names the module the key
|
|
58
|
-
// does not cover; the template
|
|
59
|
-
// WHICH of the things they asked for needs it.
|
|
60
|
-
throw new LicenseRequiredError(options.templateName
|
|
61
|
-
? `${err.message}, required by template "${options.templateName}" → ${CONVERSION_HINT_URL}`
|
|
62
|
-
: `${err.message} → ${CONVERSION_HINT_URL}`);
|
|
110
|
+
// does not cover; the template and level are appended so the buyer
|
|
111
|
+
// can see WHICH of the things they asked for needs it.
|
|
112
|
+
throw new LicenseRequiredError(licenseErrorMessage(err, moduleName, options.templateName, options.level));
|
|
63
113
|
}
|
|
64
114
|
// Not a license-gating signal (e.g. 404 "not yet available", or the
|
|
65
115
|
// registry unreachable) — a generic failure, not the honest-denial
|
|
@@ -72,10 +122,108 @@ export async function resolvePaidModules(options) {
|
|
|
72
122
|
}
|
|
73
123
|
return bundles;
|
|
74
124
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
?
|
|
79
|
-
:
|
|
80
|
-
|
|
125
|
+
function forward(options) {
|
|
126
|
+
return {
|
|
127
|
+
...(options.registryUrl !== undefined ? { registryUrl: options.registryUrl } : {}),
|
|
128
|
+
...(options.version !== undefined ? { version: options.version } : {}),
|
|
129
|
+
...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Derive the key's level from what the registry actually answers (D4) — the
|
|
134
|
+
* level only chooses which modules to ASK FOR; every byte is still
|
|
135
|
+
* authorized by the registry per module. Nothing client-side is an
|
|
136
|
+
* entitlement claim: the JWT is never parsed here.
|
|
137
|
+
*
|
|
138
|
+
* No key ⟹ `"free"`, zero network. A present key is shape-checked first,
|
|
139
|
+
* whichever source it came from (SEC-2). Then: probe every paid member of
|
|
140
|
+
* `LEVEL_MODULES.pro`, in order — a licence error on any of them is refused
|
|
141
|
+
* (a key that does not unlock all of Pro is not silently downgraded); any
|
|
142
|
+
* other failure (404, 5xx, unreachable) is wrapped so the caller sees it
|
|
143
|
+
* could not determine the level, and is told the escape hatch (`--modules`).
|
|
144
|
+
* Then probe `multi-tenant` (what Pro Plus adds beyond Pro): a licence error
|
|
145
|
+
* there is the expected "this key is Pro, not Pro Plus" answer, not a
|
|
146
|
+
* refusal.
|
|
147
|
+
*/
|
|
148
|
+
export async function deriveLevel(options) {
|
|
149
|
+
const token = options.licenseKey ?? process.env["AILK_LICENSE_KEY"];
|
|
150
|
+
if (token === undefined || token.length === 0) {
|
|
151
|
+
return { level: "free", bundles: new Map() };
|
|
152
|
+
}
|
|
153
|
+
assertLicenseKeyShape(token);
|
|
154
|
+
const bundles = new Map();
|
|
155
|
+
const proModules = LEVEL_MODULES.pro.filter((m) => PAID_MODULES.includes(m));
|
|
156
|
+
for (const moduleName of proModules) {
|
|
157
|
+
try {
|
|
158
|
+
const bytes = await fetchModule({ moduleName, token, ...forward(options) });
|
|
159
|
+
bundles.set(moduleName, bytes);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
if (err instanceof FetchModuleError) {
|
|
163
|
+
if (err.isLicenseError) {
|
|
164
|
+
throw new LicenseRequiredError(licenseErrorMessage(err, moduleName));
|
|
165
|
+
}
|
|
166
|
+
throw new FetchModuleError(`could not determine your licence level: ${err.message} — name the modules you want with --modules`);
|
|
167
|
+
}
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const bytes = await fetchModule({
|
|
173
|
+
moduleName: "multi-tenant",
|
|
174
|
+
token,
|
|
175
|
+
...forward(options),
|
|
176
|
+
});
|
|
177
|
+
bundles.set("multi-tenant", bytes);
|
|
178
|
+
return { level: "pro-plus", bundles };
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
if (err instanceof FetchModuleError) {
|
|
182
|
+
if (err.isLicenseError) {
|
|
183
|
+
return { level: "pro", bundles };
|
|
184
|
+
}
|
|
185
|
+
throw new FetchModuleError(`could not determine your licence level: ${err.message} — name the modules you want with --modules`);
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The one entry point that turns `--modules` + the key into the effective
|
|
192
|
+
* module set and its resolved bundles (D5) — `programmatic.ts`'s `scaffold()`
|
|
193
|
+
* calls this for every run except a named `--template` alias (NFR-4).
|
|
194
|
+
*
|
|
195
|
+
* A named list with a non-addition (the KEEP branch, D2) resolves it exactly
|
|
196
|
+
* as today, with no level derived and no extra request (NFR-2). Otherwise the
|
|
197
|
+
* level is derived first, the set composed from it, and any named addition
|
|
198
|
+
* not already fetched during the level probe is resolved on top.
|
|
199
|
+
*/
|
|
200
|
+
export async function resolveModuleSet(options) {
|
|
201
|
+
const namedList = options.named ?? [];
|
|
202
|
+
const additions = [...ADD_ONS, ...OPT_IN_MODULES];
|
|
203
|
+
const isKeep = namedList.some((m) => !additions.includes(m));
|
|
204
|
+
if (isKeep) {
|
|
205
|
+
const modules = composeModuleSet(namedList, "free");
|
|
206
|
+
const { paid } = classifyModules(modules);
|
|
207
|
+
const bundles = await resolvePaidModules({
|
|
208
|
+
paidModules: paid,
|
|
209
|
+
licenseKey: options.licenseKey,
|
|
210
|
+
...forward(options),
|
|
211
|
+
});
|
|
212
|
+
return { modules, bundles };
|
|
213
|
+
}
|
|
214
|
+
const { level, bundles } = await deriveLevel(options);
|
|
215
|
+
const modules = composeModuleSet(namedList, level);
|
|
216
|
+
const { paid } = classifyModules(modules);
|
|
217
|
+
const missing = paid.filter((m) => !bundles.has(m));
|
|
218
|
+
if (missing.length > 0) {
|
|
219
|
+
const extra = await resolvePaidModules({
|
|
220
|
+
paidModules: missing,
|
|
221
|
+
licenseKey: options.licenseKey,
|
|
222
|
+
level,
|
|
223
|
+
...forward(options),
|
|
224
|
+
});
|
|
225
|
+
for (const [k, v] of extra)
|
|
226
|
+
bundles.set(k, v);
|
|
227
|
+
}
|
|
228
|
+
return { modules, bundles, level };
|
|
81
229
|
}
|
|
@@ -1,19 +1,3 @@
|
|
|
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
1
|
import { type ModuleName } from "../parse-args.js";
|
|
18
2
|
/** Modules scaffolded from `templates/` with no license — today's free path. */
|
|
19
3
|
export declare const FREE_MODULES: readonly ModuleName[];
|
|
@@ -24,6 +8,51 @@ export declare const FREE_MODULES: readonly ModuleName[];
|
|
|
24
8
|
* from `RECOGNIZED_MODULES` silently.
|
|
25
9
|
*/
|
|
26
10
|
export declare const PAID_MODULES: readonly ModuleName[];
|
|
11
|
+
/**
|
|
12
|
+
* The levels a key can be at (#6759 D2). A value, never read from a flag, a
|
|
13
|
+
* manifest or a key claim — it is always DERIVED, by `deriveLevel`
|
|
14
|
+
* (`module-license-gate.ts`), from what the registry actually answers.
|
|
15
|
+
*/
|
|
16
|
+
export declare const LEVELS: readonly ["free", "pro", "pro-plus"];
|
|
17
|
+
export type Level = (typeof LEVELS)[number];
|
|
18
|
+
export declare const LEVEL_LABEL: Readonly<Record<Level, string>>;
|
|
19
|
+
/**
|
|
20
|
+
* Bought per site on top of a paid level — never switched on BY a level
|
|
21
|
+
* (OFFERINGS §4a). Naming one in `--modules` adds it without trimming
|
|
22
|
+
* (`composeModuleSet`), and it is refused below Pro naming the level it is
|
|
23
|
+
* an add-on ON TOP of.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ADD_ONS: readonly ModuleName[];
|
|
26
|
+
/**
|
|
27
|
+
* Included at Pro and above (the licence manifest's `pro` tier), but not
|
|
28
|
+
* switched on by default at any level (D1) — an authed app surface
|
|
29
|
+
* contributing no PUBLIC page to a marketing site. Naming it in `--modules`
|
|
30
|
+
* adds it without trimming, exactly like an add-on.
|
|
31
|
+
*/
|
|
32
|
+
export declare const OPT_IN_MODULES: readonly ModuleName[];
|
|
33
|
+
/**
|
|
34
|
+
* What a key at each level switches on with no `--modules` (D2). Derived —
|
|
35
|
+
* every list is `RECOGNIZED_MODULES.filter(...)`, so declaration order is
|
|
36
|
+
* preserved and a module added to the vocabulary later lands in `pro` by
|
|
37
|
+
* derivation (loud: `module-tier.test.ts`'s table test pins the exact sets).
|
|
38
|
+
*/
|
|
39
|
+
export declare const LEVEL_MODULES: Readonly<Record<Level, readonly ModuleName[]>>;
|
|
40
|
+
/**
|
|
41
|
+
* Which level (or add-on status) a module belongs to (D2). `"free"` for
|
|
42
|
+
* `FREE_MODULES`, `"add-on"` for `ADD_ONS`, `"pro-plus"` for
|
|
43
|
+
* `PRO_PLUS_MODULES`, else `"pro"` (`workspace` included — D1). Pure.
|
|
44
|
+
*/
|
|
45
|
+
export declare function levelOf(m: ModuleName): Level | "add-on";
|
|
46
|
+
/**
|
|
47
|
+
* The pure core of FR-2/FR-3 (D2): compose the effective module set from
|
|
48
|
+
* what `--modules` named and the key's derived level. Naming any module that
|
|
49
|
+
* is not an addition (`ADDITIONS`) is a KEEP — it names exactly the set,
|
|
50
|
+
* unchanged from today (NFR-2), and the level plays no part. Naming only
|
|
51
|
+
* additions (or nothing) takes the level's default plus those additions.
|
|
52
|
+
* Never refuses — refusals belong to the gate (`resolveModuleSet`), because
|
|
53
|
+
* whether a named module is covered is a registry answer, not a table lookup.
|
|
54
|
+
*/
|
|
55
|
+
export declare function composeModuleSet(named: readonly ModuleName[] | undefined, level: Level): ModuleName[];
|
|
27
56
|
export interface ModuleClassification {
|
|
28
57
|
free: ModuleName[];
|
|
29
58
|
paid: ModuleName[];
|
package/dist/lib/module-tier.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* (`parse-args.ts`) defaults it to "paid" until someone deliberately adds it
|
|
15
15
|
* to `FREE_MODULES`, rather than silently shipping it free.
|
|
16
16
|
*/
|
|
17
|
+
import { normalizeModules } from "../module-architecture.js";
|
|
17
18
|
import { RECOGNIZED_MODULES } from "../parse-args.js";
|
|
18
19
|
/** Modules scaffolded from `templates/` with no license — today's free path. */
|
|
19
20
|
export const FREE_MODULES = ["marketing"];
|
|
@@ -24,6 +25,81 @@ export const FREE_MODULES = ["marketing"];
|
|
|
24
25
|
* from `RECOGNIZED_MODULES` silently.
|
|
25
26
|
*/
|
|
26
27
|
export const PAID_MODULES = RECOGNIZED_MODULES.filter((m) => !FREE_MODULES.includes(m));
|
|
28
|
+
/**
|
|
29
|
+
* The levels a key can be at (#6759 D2). A value, never read from a flag, a
|
|
30
|
+
* manifest or a key claim — it is always DERIVED, by `deriveLevel`
|
|
31
|
+
* (`module-license-gate.ts`), from what the registry actually answers.
|
|
32
|
+
*/
|
|
33
|
+
export const LEVELS = ["free", "pro", "pro-plus"];
|
|
34
|
+
export const LEVEL_LABEL = {
|
|
35
|
+
free: "Free",
|
|
36
|
+
pro: "Pro",
|
|
37
|
+
"pro-plus": "Pro Plus",
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Bought per site on top of a paid level — never switched on BY a level
|
|
41
|
+
* (OFFERINGS §4a). Naming one in `--modules` adds it without trimming
|
|
42
|
+
* (`composeModuleSet`), and it is refused below Pro naming the level it is
|
|
43
|
+
* an add-on ON TOP of.
|
|
44
|
+
*/
|
|
45
|
+
export const ADD_ONS = ["commerce"];
|
|
46
|
+
/**
|
|
47
|
+
* Included at Pro and above (the licence manifest's `pro` tier), but not
|
|
48
|
+
* switched on by default at any level (D1) — an authed app surface
|
|
49
|
+
* contributing no PUBLIC page to a marketing site. Naming it in `--modules`
|
|
50
|
+
* adds it without trimming, exactly like an add-on.
|
|
51
|
+
*/
|
|
52
|
+
export const OPT_IN_MODULES = ["workspace"];
|
|
53
|
+
/** The Pro Plus membership: what Pro Plus switches on beyond Pro. */
|
|
54
|
+
const PRO_PLUS_MODULES = ["multi-tenant"];
|
|
55
|
+
/** Named modules that never trim the level default when composing a set (D2). */
|
|
56
|
+
const ADDITIONS = [...ADD_ONS, ...OPT_IN_MODULES];
|
|
57
|
+
const PRO_EXCLUDED = new Set([...ADDITIONS, ...PRO_PLUS_MODULES]);
|
|
58
|
+
/**
|
|
59
|
+
* What a key at each level switches on with no `--modules` (D2). Derived —
|
|
60
|
+
* every list is `RECOGNIZED_MODULES.filter(...)`, so declaration order is
|
|
61
|
+
* preserved and a module added to the vocabulary later lands in `pro` by
|
|
62
|
+
* derivation (loud: `module-tier.test.ts`'s table test pins the exact sets).
|
|
63
|
+
*/
|
|
64
|
+
export const LEVEL_MODULES = Object.freeze({
|
|
65
|
+
free: [...FREE_MODULES],
|
|
66
|
+
pro: RECOGNIZED_MODULES.filter((m) => FREE_MODULES.includes(m) ||
|
|
67
|
+
(PAID_MODULES.includes(m) && !PRO_EXCLUDED.has(m))),
|
|
68
|
+
"pro-plus": RECOGNIZED_MODULES.filter((m) => FREE_MODULES.includes(m) ||
|
|
69
|
+
(PAID_MODULES.includes(m) && !ADDITIONS.includes(m))),
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Which level (or add-on status) a module belongs to (D2). `"free"` for
|
|
73
|
+
* `FREE_MODULES`, `"add-on"` for `ADD_ONS`, `"pro-plus"` for
|
|
74
|
+
* `PRO_PLUS_MODULES`, else `"pro"` (`workspace` included — D1). Pure.
|
|
75
|
+
*/
|
|
76
|
+
export function levelOf(m) {
|
|
77
|
+
if (FREE_MODULES.includes(m))
|
|
78
|
+
return "free";
|
|
79
|
+
if (ADD_ONS.includes(m))
|
|
80
|
+
return "add-on";
|
|
81
|
+
if (PRO_PLUS_MODULES.includes(m))
|
|
82
|
+
return "pro-plus";
|
|
83
|
+
return "pro";
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The pure core of FR-2/FR-3 (D2): compose the effective module set from
|
|
87
|
+
* what `--modules` named and the key's derived level. Naming any module that
|
|
88
|
+
* is not an addition (`ADDITIONS`) is a KEEP — it names exactly the set,
|
|
89
|
+
* unchanged from today (NFR-2), and the level plays no part. Naming only
|
|
90
|
+
* additions (or nothing) takes the level's default plus those additions.
|
|
91
|
+
* Never refuses — refusals belong to the gate (`resolveModuleSet`), because
|
|
92
|
+
* whether a named module is covered is a registry answer, not a table lookup.
|
|
93
|
+
*/
|
|
94
|
+
export function composeModuleSet(named, level) {
|
|
95
|
+
const namedList = [...(named ?? [])];
|
|
96
|
+
const keep = namedList.filter((m) => !ADDITIONS.includes(m));
|
|
97
|
+
if (keep.length > 0) {
|
|
98
|
+
return normalizeModules(namedList);
|
|
99
|
+
}
|
|
100
|
+
const add = namedList.filter((m) => ADDITIONS.includes(m));
|
|
101
|
+
return normalizeModules([...LEVEL_MODULES[level], ...add]);
|
|
102
|
+
}
|
|
27
103
|
/**
|
|
28
104
|
* Split a requested (already-validated, `ModuleName`) module set into its
|
|
29
105
|
* free and paid subsets, each preserving the input's relative order.
|