create-ailk 0.9.0 → 0.11.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.
Files changed (104) hide show
  1. package/README.md +10 -5
  2. package/dist/cli.js +17 -2
  3. package/dist/content-payloads.d.ts +37 -0
  4. package/dist/content-payloads.js +64 -0
  5. package/dist/lib/fetch-module.d.ts +3 -3
  6. package/dist/lib/fetch-module.js +11 -5
  7. package/dist/lib/fetch-template.d.ts +1 -1
  8. package/dist/lib/fetch-template.js +7 -4
  9. package/dist/lib/module-license-gate.d.ts +29 -23
  10. package/dist/lib/module-license-gate.js +98 -0
  11. package/dist/module-architecture.d.ts +9 -0
  12. package/dist/module-architecture.js +46 -14
  13. package/dist/parse-args.d.ts +2 -0
  14. package/dist/parse-args.js +7 -0
  15. package/dist/programmatic.js +12 -4
  16. package/dist/starter-grid.d.ts +4 -67
  17. package/dist/starter-grid.js +6 -84
  18. package/dist/surfaces.js +2 -3
  19. package/dist/template-manifest.d.ts +1 -1
  20. package/dist/template-manifest.js +5 -5
  21. package/package.json +6 -4
  22. package/release-scope.json +25 -0
  23. package/site-templates/designs/minimalism/sample/README.md +2 -2
  24. package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +1 -1
  25. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +4 -3
  26. package/templates/.claude/agents/web.md +10 -8
  27. package/templates/.claude/rules/architecture.md +10 -7
  28. package/templates/.claude/settings.json +12 -0
  29. package/templates/.env.example +24 -3
  30. package/templates/CLAUDE.md +38 -22
  31. package/templates/README.md +12 -12
  32. package/templates/apps/api/jest.config.cjs +1 -0
  33. package/templates/apps/api/package.json +1 -0
  34. package/templates/apps/mcp/CLAUDE.md +23 -10
  35. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +160 -17
  36. package/templates/apps/mcp/__tests__/index.test.ts +118 -1
  37. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +425 -5
  38. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +67 -3
  39. package/templates/apps/mcp/__tests__/server.test.ts +162 -1
  40. package/templates/apps/mcp/src/index.ts +35 -3
  41. package/templates/apps/mcp/src/lib/commerce-claim.ts +193 -24
  42. package/templates/apps/mcp/src/server.ts +77 -5
  43. package/templates/apps/mcp/src/tools/purchase.ts +1 -1
  44. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +15 -0
  45. package/templates/apps/web/app/.well-known/mcp.json/route.ts +10 -3
  46. package/templates/apps/web/src/lib/commerce-claim.ts +15 -6
  47. package/templates/content/_site.mdx +23 -4
  48. package/templates/database/CHANGELOG.md +7 -0
  49. package/templates/database/package.json +1 -1
  50. package/templates/package.json +2 -0
  51. package/versions.json +34 -33
  52. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  53. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  54. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  55. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  56. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  57. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  58. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  59. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  60. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  61. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  62. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  63. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  64. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  65. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  66. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  67. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  68. package/site-templates/aliases/minimalism-site.TEMPLATE-MANIFEST.json +0 -538
  69. package/templates/.claude/skills/README.md +0 -37
  70. package/templates/.claude/skills/add-auth/SKILL.md +0 -261
  71. package/templates/.claude/skills/add-auth/references/env-vars.md +0 -98
  72. package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +0 -172
  73. package/templates/.claude/skills/add-page/SKILL.md +0 -260
  74. package/templates/.claude/skills/add-page/references/page-types.md +0 -124
  75. package/templates/.claude/skills/add-page/scripts/site-pages.mjs +0 -87
  76. package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +0 -99
  77. package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +0 -18
  78. package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +0 -43
  79. package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +0 -52
  80. package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +0 -38
  81. package/templates/.claude/skills/check-site/SKILL.md +0 -235
  82. package/templates/.claude/skills/check-site/references/common-errors.md +0 -113
  83. package/templates/.claude/skills/check-site/references/findings-glossary.md +0 -140
  84. package/templates/.claude/skills/check-site/references/severity-levels.md +0 -114
  85. package/templates/.claude/skills/fill-page/SKILL.md +0 -199
  86. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +0 -80
  87. package/templates/.claude/skills/fill-page/references/section-sources.md +0 -41
  88. package/templates/.claude/skills/setup-database/SKILL.md +0 -156
  89. package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +0 -154
  90. package/templates/.claude/skills/setup-deploy/SKILL.md +0 -275
  91. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +0 -192
  92. package/templates/.claude/skills/setup-deploy/references/runbook-template.md +0 -204
  93. package/templates/.claude/skills/setup-deploy/references/vercel-json-template.md +0 -98
  94. package/templates/.claude/skills/update-page/SKILL.md +0 -117
  95. package/templates/.claude/skills/update-page/scripts/update-page.ts +0 -640
  96. package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +0 -31
  97. package/templates/.claude/skills/upgrade-plan/SKILL.md +0 -121
  98. package/templates/.claude/skills/upgrade-plan/references/options.md +0 -24
  99. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +0 -527
  100. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +0 -8
  101. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +0 -12
  102. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +0 -12
  103. package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +0 -75
  104. package/templates/apps/mcp/src/lib/require-paid-tier.ts +0 -44
package/README.md CHANGED
@@ -52,9 +52,9 @@ pnpm create ailk@latest my-site --shape organization-service --license <key>
52
52
 
53
53
  `--design minimalism` picks the other look and needs a Pro or Pro Plus key;
54
54
  `standard` (the default) needs none. `--template <alias>` scaffolds one of the
55
- four named aliases (`marketing-site-free`, `marketing-site`, `saas-product`,
56
- `minimalism-site`) — each fixes a shape, a design and its own module list — and
57
- cannot be combined with `--shape` or `--design`.
55
+ three named aliases (`marketing-site-free`, `marketing-site`, `saas-product`) —
56
+ each fixes a shape, a design and its own module list — and cannot be combined
57
+ with `--shape` or `--design`.
58
58
 
59
59
  ### Add a purchased module to an existing site
60
60
 
@@ -275,8 +275,13 @@ gate runs before anything is copied (`scaffold()` in `src/programmatic.ts`), so
275
275
  a refused run leaves no partial scaffold behind.
276
276
 
277
277
  `AILK_MODULE_REGISTRY_URL` is optional: `fetchModule` uses an explicitly passed
278
- `registryUrl`, then that variable, then the built-in
279
- `DEFAULT_AILK_MODULE_REGISTRY_URL` (`src/lib/fetch-module.ts`).
278
+ `registryUrl`, then that variable, then `https://api.ailaunchkit.ai/v1/modules`
279
+ (`DEFAULT_AILK_MODULE_REGISTRY_URL`, `src/lib/fetch-module.ts`).
280
+ `AILK_TEMPLATE_REGISTRY_URL` is optional the same way: `fetchTemplate` uses an
281
+ explicitly passed `registryUrl`, then that variable, then
282
+ `https://api.ailaunchkit.ai/v1/templates` (`DEFAULT_AILK_TEMPLATE_REGISTRY_URL`,
283
+ `src/lib/fetch-template.ts`). Neither client follows a redirect: a 3xx from
284
+ either registry fails the fetch before any second request is sent.
280
285
 
281
286
  ```bash
282
287
  # free-only — no key read, no network call
package/dist/cli.js CHANGED
@@ -43,9 +43,10 @@ import { ADD_ROUTE_HELP, AddRouteRefusedError, addRouteToScaffold, formatAddRout
43
43
  import { APPLY_DESIGN_HELP, applyDesign, formatApplyDesignReport, parseApplyDesignArgs, } from "./apply-design.js";
44
44
  import { CLAUDE_MD_CHECK_PATH } from "./claude-md.js";
45
45
  import { buildScaffoldMarkers } from "./copy.js";
46
+ import { assertReleased } from "./lib/module-license-gate.js";
46
47
  import { FREE_PREREQUISITES_NOTE, LICENSED_PREREQUISITES_NOTE, isLicensedInstall, proCallout, } from "./lib/pro-callout.js";
47
48
  import { LICENSE_SET_HELP, runLicenseSet } from "./license-set.js";
48
- import { helpText, parseCliArgs, parseModules, resolveCliShape, toScaffoldOptions, } from "./parse-args.js";
49
+ import { PLUGIN_INSTALL_LINES, helpText, parseCliArgs, parseModules, resolveCliShape, toScaffoldOptions, } from "./parse-args.js";
49
50
  import { addModule, formatSurfaceDrift, scaffold, syncArchitectureSync, syncCatalogSync, syncClaudeMdSync, syncRoutesSync, } from "./programmatic.js";
50
51
  import { CORE_SURFACE_PATHS, SURFACE_ALIASES } from "./surfaces.js";
51
52
  import { SYNC_ROUTES_HELP, formatSyncRoutesReport, parseSyncRoutesArgs, } from "./sync-routes.js";
@@ -207,7 +208,17 @@ async function main() {
207
208
  }
208
209
  // No silent default (#6754 D2/D3): a scaffold run naming neither --shape
209
210
  // nor --template asks for a shape on a terminal and is refused without one.
210
- const opts = await resolveCliShape(parseCliArgs(argv, { interactive: process.stdin.isTTY === true }), { input: process.stdin, output: process.stdout });
211
+ // A --design or --modules outside the release scope is refused before that
212
+ // prompt asks anything (#6984); scaffold() checks the full run again.
213
+ const parsed = parseCliArgs(argv, { interactive: process.stdin.isTTY === true });
214
+ if (parsed.needsShapePrompt && !parsed.help && !parsed.version) {
215
+ assertReleased("designs", [parsed.design]);
216
+ assertReleased("modules", parsed.modules ?? []);
217
+ }
218
+ const opts = await resolveCliShape(parsed, {
219
+ input: process.stdin,
220
+ output: process.stdout,
221
+ });
211
222
  // Deprecation notice for a retired `--surfaces` id (#5563 D2). `surfaces.ts`
212
223
  // stays pure — `canonicalizeSurface` only resolves the alias — so this is
213
224
  // the one place that prints: compare the raw `--surfaces` argv token
@@ -370,6 +381,10 @@ async function main() {
370
381
  lines.push("Open http://localhost:3000 to see the home page.");
371
382
  lines.push("Adding auth, saved flows, checkout or database-backed content later? README.md \u00a7 \"When you need a database\" has the one-command setup.");
372
383
  lines.push("");
384
+ lines.push("Claude Code: the AI Launch Kit skills come from the ailk plugin. Trust this folder and Claude Code loads it, or run in a session:");
385
+ for (const line of PLUGIN_INSTALL_LINES)
386
+ lines.push(` ${line}`);
387
+ lines.push("");
373
388
  lines.push(`Route surfaces: ${CORE_SURFACE_PATHS.join(", ")}` +
374
389
  (surfaces.length > 0 ? ` + ${surfaces.join(", ")}` : ""));
375
390
  lines.push("Add another with `create-ailk add-route <id>`, or re-run with --surfaces <id>.");
@@ -0,0 +1,37 @@
1
+ /** The issuer's content kinds (spec 7110 § R1), pinned equal to CONTENT_KINDS by the issuer's test. */
2
+ export declare const CONTENT_PAYLOAD_KINDS: readonly ["skill", "profile", "template-set", "instructions"];
3
+ export type ContentPayloadKind = (typeof CONTENT_PAYLOAD_KINDS)[number];
4
+ export interface ContentPayload {
5
+ readonly kind: ContentPayloadKind;
6
+ /** ^[a-z0-9][a-z0-9-]*$ — the route's :name and the store's path segment. */
7
+ readonly name: string;
8
+ /** The ONE paid feature the issuer's CONTENT_FEATURES row maps this name to. */
9
+ readonly feature: string;
10
+ /** SemVer — the content version, never create-ailk's (spec 7131 § D1). */
11
+ readonly version: string;
12
+ /**
13
+ * 64-hex `contentDigest` of `files` at `version` (§ D5). A change to any
14
+ * payload byte needs a new `version` AND a new `digest`; the build refuses
15
+ * until this agrees with the tree.
16
+ */
17
+ readonly digest: string;
18
+ /** Repo-relative, POSIX; each file's source is `${sourceRoot}/${file}`. */
19
+ readonly sourceRoot: string;
20
+ /** Archive paths, POSIX, relative, sorted, unique — listed, never globbed. */
21
+ readonly files: readonly string[];
22
+ }
23
+ export declare const CONTENT_PAYLOADS: readonly ContentPayload[];
24
+ /** The producer-side cap on one payload's archive (spec 7131 § D6). */
25
+ export declare const MAX_CONTENT_PAYLOAD_BYTES: number;
26
+ /** The keyed route's public base; `AILK_CONTENT_REGISTRY_URL` overrides it. */
27
+ export declare const DEFAULT_AILK_CONTENT_REGISTRY_URL = "https://api.ailaunchkit.ai/v1/content";
28
+ /**
29
+ * sha256 over the sorted `${path}\0${sha256hex(bytes)}\n` lines (§ D5).
30
+ * Covers archive paths and file bytes, never the archive's own bytes — gzip
31
+ * and tar headers differ between machines and must not decide whether the
32
+ * content changed.
33
+ */
34
+ export declare function contentDigest(files: ReadonlyArray<{
35
+ path: string;
36
+ bytes: Uint8Array;
37
+ }>): string;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The content-payload manifest (spec 7131 § D2): every licence-gated content
3
+ * payload the issuer's keyed route `GET /v1/content/:kind/:name/:version`
4
+ * serves (spec 7110) — its kind, name, feature, content version, content
5
+ * digest and source files. The ONE place the builder
6
+ * (`scripts/build-content-payloads.mjs`), its `--verify` leg and the issuer's
7
+ * `CONTENT_FEATURES` agreement test read.
8
+ *
9
+ * A build-time registry, not programmatic API: deliberately not exported from
10
+ * `src/index.ts`. It carries names, a feature, a version and a digest — never
11
+ * a payload byte — so the public tarball's `dist/content-payloads.js` ships no
12
+ * paid content (spec 7131 § 7, AC-SPB-05).
13
+ *
14
+ * create-ailk is `oss`-tier and cannot import the issuer or the licence
15
+ * manifest, so the kind list is a pinned COPY of the issuer's `CONTENT_KINDS`
16
+ * and `feature` is a plain string; the issuer's `module-features.test.ts`
17
+ * fails when either drifts.
18
+ */
19
+ import { createHash } from "node:crypto";
20
+ /** The issuer's content kinds (spec 7110 § R1), pinned equal to CONTENT_KINDS by the issuer's test. */
21
+ export const CONTENT_PAYLOAD_KINDS = [
22
+ "skill",
23
+ "profile",
24
+ "template-set",
25
+ "instructions",
26
+ ];
27
+ export const CONTENT_PAYLOADS = Object.freeze([
28
+ Object.freeze({
29
+ kind: "template-set",
30
+ name: "scaffold-commerce",
31
+ feature: "commerce",
32
+ version: "1.0.0",
33
+ digest: "baa4ac6e86ec7e3b367eb67e77c3407da90c7ff9ef51d5f4e1185615194543ee",
34
+ sourceRoot: "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce",
35
+ files: Object.freeze([
36
+ "references/lookup-keys-template.md",
37
+ "references/marketplace-brand-pages.md",
38
+ "templates/checkout-route.template.ts",
39
+ "templates/invoice-route.template.ts",
40
+ "templates/marketplace-cta-route.tsx.tmpl",
41
+ "templates/payment-link-route.template.ts",
42
+ "templates/portal-route.template.ts",
43
+ "templates/stripe-config.template.ts",
44
+ "templates/subscription-route.template.ts",
45
+ ]),
46
+ }),
47
+ ]);
48
+ /** The producer-side cap on one payload's archive (spec 7131 § D6). */
49
+ export const MAX_CONTENT_PAYLOAD_BYTES = 256 * 1024;
50
+ /** The keyed route's public base; `AILK_CONTENT_REGISTRY_URL` overrides it. */
51
+ export const DEFAULT_AILK_CONTENT_REGISTRY_URL = "https://api.ailaunchkit.ai/v1/content";
52
+ const sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
53
+ /**
54
+ * sha256 over the sorted `${path}\0${sha256hex(bytes)}\n` lines (§ D5).
55
+ * Covers archive paths and file bytes, never the archive's own bytes — gzip
56
+ * and tar headers differ between machines and must not decide whether the
57
+ * content changed.
58
+ */
59
+ export function contentDigest(files) {
60
+ const lines = files
61
+ .map(({ path, bytes }) => `${path}\0${sha256Hex(bytes)}\n`)
62
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
63
+ return sha256Hex(lines.join(""));
64
+ }
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_AILK_MODULE_REGISTRY_URL = "https://ailaunchkit.ai/api/modules";
1
+ export declare const DEFAULT_AILK_MODULE_REGISTRY_URL = "https://api.ailaunchkit.ai/v1/modules";
2
2
  export interface FetchModuleOptions {
3
3
  moduleName: string;
4
4
  /** JWT used as `Authorization: Bearer <token>`. Must be non-empty. */
@@ -11,10 +11,10 @@ export interface FetchModuleOptions {
11
11
  fetchImpl?: typeof fetch;
12
12
  }
13
13
  export declare class FetchModuleError extends Error {
14
- /** True iff the failure was a license-gating signal (HTTP 401). */
14
+ /** True iff the failure was a license-gating signal (HTTP 401 or 403). */
15
15
  readonly isLicenseError: boolean;
16
16
  constructor(message: string,
17
- /** True iff the failure was a license-gating signal (HTTP 401). */
17
+ /** True iff the failure was a license-gating signal (HTTP 401 or 403). */
18
18
  isLicenseError?: boolean);
19
19
  }
20
20
  export declare function fetchModule(options: FetchModuleOptions): Promise<Uint8Array>;
@@ -9,8 +9,14 @@
9
9
  * distribution only.
10
10
  * - Empty-token guard before any network call (defense in depth — the
11
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.
12
+ * - Status-code mapping: 401 → license invalid/revoked/lapsed, 403 →
13
+ * not included / term ended, 404 → included, but that release's bundle
14
+ * not yet uploaded, other 4xx/5xx → upstream body. The issuer answers
15
+ * 403 before 404 (spec 7110 § D4), so a 404 reaches only a key that
16
+ * holds the module's feature.
17
+ * - Default registry: the product API gateway (`api.ailaunchkit.ai`, routed
18
+ * to the licence issuer's `/v1/modules`); `AILK_MODULE_REGISTRY_URL`
19
+ * overrides it.
14
20
  *
15
21
  * Deliberately DOES NOT depend on `@working-theory/license` or
16
22
  * `@working-theory/cli` — both are `publishConfig.access: "restricted"` and
@@ -37,7 +43,7 @@
37
43
  */
38
44
  import pkg from "../../package.json" with { type: "json" };
39
45
  import { assertContentLengthWithinLimit, assertHttpsRegistryUrl, fetchTimeoutSignal, } from "./fetch-guard.js";
40
- export const DEFAULT_AILK_MODULE_REGISTRY_URL = "https://ailaunchkit.ai/api/modules";
46
+ export const DEFAULT_AILK_MODULE_REGISTRY_URL = "https://api.ailaunchkit.ai/v1/modules";
41
47
  /**
42
48
  * This package's own `package.json` `"version"`. Deliberately a static JSON
43
49
  * import (`resolveJsonModule`), not `import.meta.url` + `fileURLToPath` (the
@@ -53,7 +59,7 @@ const CREATE_AILK_VERSION = pkg.version;
53
59
  export class FetchModuleError extends Error {
54
60
  isLicenseError;
55
61
  constructor(message,
56
- /** True iff the failure was a license-gating signal (HTTP 401). */
62
+ /** True iff the failure was a license-gating signal (HTTP 401 or 403). */
57
63
  isLicenseError = false) {
58
64
  super(message);
59
65
  this.isLicenseError = isLicenseError;
@@ -108,7 +114,7 @@ export async function fetchModule(options) {
108
114
  /* isLicenseError */ true);
109
115
  }
110
116
  if (response.status === 404) {
111
- throw new FetchModuleError(`"${moduleName}" module content not yet available — ping us at hello@ailaunchkit.ai`);
117
+ throw new FetchModuleError(`"${moduleName}" module content not yet available for create-ailk ${version} — your licence includes it, but that release's bundle has not been uploaded yet; ping us at hello@ailaunchkit.ai`);
112
118
  }
113
119
  if (!response.ok) {
114
120
  const body = await safeReadBody(response);
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_AILK_TEMPLATE_REGISTRY_URL = "https://ailaunchkit.ai/api/templates";
1
+ export declare const DEFAULT_AILK_TEMPLATE_REGISTRY_URL = "https://api.ailaunchkit.ai/v1/templates";
2
2
  export interface FetchTemplateOptions {
3
3
  templateName: string;
4
4
  /** JWT used as `Authorization: Bearer <token>`. Must be non-empty. */
@@ -3,13 +3,16 @@
3
3
  * D1, § 6).
4
4
  *
5
5
  * The client half of `GET /v1/templates/:name/:version` (#5480's route),
6
- * reached publicly at `/api/templates/:path*`. Posture is
6
+ * reached at `https://api.ailaunchkit.ai/v1/templates/:name/:version` through
7
+ * the product API gateway. Posture is
7
8
  * `fetch-module.ts`'s, mirrored one-for-one rather than generalised into a
8
9
  * shared helper: bearer-JWT auth, `redirect: "error"` so a redirected
9
10
  * response cannot drop the bearer token onto an unintended host, an
10
11
  * empty-token guard before any network call, and the same status mapping —
11
12
  * 401 → licence invalid/revoked/lapsed, 403 → not included / term ended,
12
- * 404 → content not yet available, other non-2xx → the upstream body.
13
+ * 404 → included, but that release's bundle not yet uploaded, other non-2xx
14
+ * → the upstream body. The issuer answers 403 before 404 (spec 7110 § D4),
15
+ * so a 404 reaches only a key that holds the template's whole union.
13
16
  *
14
17
  * ONE word differs, and it is the whole reason this is a sibling rather
15
18
  * than a second call site of `fetchModule` (spec 5481 § 3 DD5): the 404
@@ -27,7 +30,7 @@
27
30
  */
28
31
  import pkg from "../../package.json" with { type: "json" };
29
32
  import { assertContentLengthWithinLimit, assertHttpsRegistryUrl, fetchTimeoutSignal, } from "./fetch-guard.js";
30
- export const DEFAULT_AILK_TEMPLATE_REGISTRY_URL = "https://ailaunchkit.ai/api/templates";
33
+ export const DEFAULT_AILK_TEMPLATE_REGISTRY_URL = "https://api.ailaunchkit.ai/v1/templates";
31
34
  /**
32
35
  * This package's own `package.json` `"version"` — a static JSON import for
33
36
  * the reason `fetch-module.ts`'s `CREATE_AILK_VERSION` doc gives: `import.meta`
@@ -95,7 +98,7 @@ export async function fetchTemplate(options) {
95
98
  /* isLicenseError */ true);
96
99
  }
97
100
  if (response.status === 404) {
98
- throw new FetchTemplateError(`"${templateName}" template content not yet available — ping us at hello@ailaunchkit.ai`);
101
+ throw new FetchTemplateError(`"${templateName}" template content not yet available for create-ailk ${version} — your licence includes it, but that release's bundle has not been uploaded yet; ping us at hello@ailaunchkit.ai`);
99
102
  }
100
103
  if (!response.ok) {
101
104
  const body = await safeReadBody(response);
@@ -1,26 +1,3 @@
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
- * 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.
23
- */
24
1
  import type { ModuleName } from "../parse-args.js";
25
2
  import { type Level } from "./module-tier.js";
26
3
  /**
@@ -38,6 +15,35 @@ export declare const CONVERSION_HINT_URL = "https://ailaunchkit.ai/pricing";
38
15
  export declare class LicenseRequiredError extends Error {
39
16
  constructor(message: string);
40
17
  }
18
+ /** The five axes `release-scope.json` declares (spec 6977 D3). */
19
+ export type ReleaseAxis = "templates" | "shapes" | "designs" | "modules" | "levels";
20
+ /** `gated` and `deferred` are both refused; only a `gated` one gets negative checks. */
21
+ export type ReleaseStatus = "released" | "gated" | "deferred";
22
+ export type ReleaseScope = Readonly<Record<ReleaseAxis, Readonly<Record<string, ReleaseStatus>>>>;
23
+ /**
24
+ * The package-root `release-scope.json` (shipped in `files`). A static JSON
25
+ * import, not an `import.meta.url` read, for `fetch-module.ts`'s reason: jest
26
+ * loads this module directly. `release-scope.test.ts` pins every registry
27
+ * name to an entry here (R5).
28
+ */
29
+ export declare const RELEASE_SCOPE: ReleaseScope;
30
+ /** Thrown for a surface outside the release scope — a refusal, not a licence state. */
31
+ export declare class ReleaseScopeError extends Error {
32
+ constructor(message: string);
33
+ }
34
+ /**
35
+ * Refuse any of `names` that `scope[axis]` marks `gated` or `deferred`, with
36
+ * the not-available message `parse-args.ts` gives a reserved shape. A name
37
+ * the scope does not list (a customer's own design, say) is not refused.
38
+ * Bypassed when `AILK_RELEASE_SCOPE=ignore` — this package's jest setup and
39
+ * an operator's own run; a published CLI never sets it.
40
+ *
41
+ * Called on the scaffold path only (`scaffold()`, `cli.ts`, and the level
42
+ * check in `resolveModuleSet`) — never from `parseModules`, so `add-module`
43
+ * and `--sync-architecture` keep working on a site that already carries a
44
+ * gated module.
45
+ */
46
+ export declare function assertReleased(axis: ReleaseAxis, names: readonly (string | undefined)[], scope?: ReleaseScope): void;
41
47
  /**
42
48
  * Design names every level renders (#6760 D1). Anything else in
43
49
  * `STARTER_DESIGNS` is Pro and up. A path design's Free-ness is decided by
@@ -1,3 +1,31 @@
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
+ * 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.
23
+ *
24
+ * And the release-scope gate (#6984, spec 6977 D3): `assertReleased` refuses
25
+ * a template, shape, design, module or level that `release-scope.json` marks
26
+ * `gated` or `deferred`, on the scaffold path only.
27
+ */
28
+ import releaseScopeJson from "../../release-scope.json" with { type: "json" };
1
29
  import { FetchModuleError, fetchModule } from "./fetch-module.js";
2
30
  import { assertLicenseKeyShape } from "./license-key-files.js";
3
31
  import { ADD_ONS, LEVEL_LABEL, LEVEL_MODULES, OPT_IN_MODULES, PAID_MODULES, classifyModules, composeModuleSet, levelOf, } from "./module-tier.js";
@@ -19,6 +47,74 @@ export class LicenseRequiredError extends Error {
19
47
  this.name = "LicenseRequiredError";
20
48
  }
21
49
  }
50
+ /**
51
+ * The package-root `release-scope.json` (shipped in `files`). A static JSON
52
+ * import, not an `import.meta.url` read, for `fetch-module.ts`'s reason: jest
53
+ * loads this module directly. `release-scope.test.ts` pins every registry
54
+ * name to an entry here (R5).
55
+ */
56
+ export const RELEASE_SCOPE = releaseScopeJson;
57
+ const AXIS_NOUN = {
58
+ templates: "template",
59
+ shapes: "shape",
60
+ designs: "design",
61
+ modules: "module",
62
+ levels: "level",
63
+ };
64
+ /** Thrown for a surface outside the release scope — a refusal, not a licence state. */
65
+ export class ReleaseScopeError extends Error {
66
+ constructor(message) {
67
+ super(message);
68
+ this.name = "ReleaseScopeError";
69
+ }
70
+ }
71
+ const RELEASE_STATUSES = ["released", "gated", "deferred"];
72
+ /**
73
+ * Refuse a scope carrying a status other than `released`, `gated` or
74
+ * `deferred` — fail closed: a misspelled status never reads as released.
75
+ */
76
+ function assertValidScope(scope) {
77
+ for (const [axis, statuses] of Object.entries(scope)) {
78
+ for (const [name, status] of Object.entries(statuses)) {
79
+ if (!RELEASE_STATUSES.includes(status)) {
80
+ throw new ReleaseScopeError(`release-scope.json ${axis}."${name}" is "${String(status)}" — expected one of: ${RELEASE_STATUSES.join(", ")}`);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Refuse any of `names` that `scope[axis]` marks `gated` or `deferred`, with
87
+ * the not-available message `parse-args.ts` gives a reserved shape. A name
88
+ * the scope does not list (a customer's own design, say) is not refused.
89
+ * Bypassed when `AILK_RELEASE_SCOPE=ignore` — this package's jest setup and
90
+ * an operator's own run; a published CLI never sets it.
91
+ *
92
+ * Called on the scaffold path only (`scaffold()`, `cli.ts`, and the level
93
+ * check in `resolveModuleSet`) — never from `parseModules`, so `add-module`
94
+ * and `--sync-architecture` keep working on a site that already carries a
95
+ * gated module.
96
+ */
97
+ export function assertReleased(axis, names, scope = RELEASE_SCOPE) {
98
+ if (process.env["AILK_RELEASE_SCOPE"] === "ignore")
99
+ return;
100
+ assertValidScope(scope);
101
+ const statuses = scope[axis];
102
+ for (const name of names) {
103
+ if (name === undefined)
104
+ continue;
105
+ const status = statuses[name];
106
+ if (status !== "gated" && status !== "deferred")
107
+ continue;
108
+ const released = Object.keys(statuses).filter((n) => statuses[n] === "released");
109
+ // A level comes from the key, never a flag — so it points at what the run CAN choose.
110
+ const choose = axis === "levels"
111
+ ? `Your licence key's level is ${name}; name the modules you want with --modules (one of: ${Object.keys(scope.modules)
112
+ .filter((m) => scope.modules[m] === "released")
113
+ .join(", ")}).`
114
+ : `Choose one of: ${released.join(", ")}.`;
115
+ throw new ReleaseScopeError(`${AXIS_NOUN[axis]} "${name}" is not available yet. ${choose}`);
116
+ }
117
+ }
22
118
  /**
23
119
  * Design names every level renders (#6760 D1). Anything else in
24
120
  * `STARTER_DESIGNS` is Pro and up. A path design's Free-ness is decided by
@@ -223,6 +319,8 @@ export async function resolveModuleSet(options) {
223
319
  return { modules, bundles };
224
320
  }
225
321
  const { level, bundles } = await deriveLevel(options);
322
+ // #6984 — a gated level (Pro Plus) is refused before its defaults compose.
323
+ assertReleased("levels", [level]);
226
324
  const modules = composeModuleSet(namedList, level);
227
325
  const { paid } = classifyModules(modules);
228
326
  const missing = paid.filter((m) => !bundles.has(m));
@@ -212,6 +212,15 @@ export interface ModuleSlice {
212
212
  * leak class `scripts/check-oss-strip.sh` exists to catch (#5644).
213
213
  */
214
214
  installRoot?: string;
215
+ /**
216
+ * Paths this module's keyed content payloads occupy (src/content-payloads.ts):
217
+ * each payload file's monorepo source AND the scaffold path its skill's fetch
218
+ * writes it to. Never delivered — no build, extract or patch step reads this
219
+ * field. Declared so the paid-path leak guards, which derive from
220
+ * `ownedFiles`/`installMap`, still recognise a leaked copy after the files
221
+ * left `installMap` (#7133).
222
+ */
223
+ contentPayloadPaths?: string[];
215
224
  /**
216
225
  * Plain text `extractModuleBundle` prints to stdout after a successful
217
226
  * merge, with C0/C1 control characters (other than newline) stripped
@@ -23,6 +23,7 @@
23
23
  */
24
24
  import { existsSync, readFileSync } from "node:fs";
25
25
  import { join, sep } from "node:path";
26
+ import { CONTENT_PAYLOADS, } from "./content-payloads.js";
26
27
  import { importClosure } from "./lib/import-graph.js";
27
28
  import { PAID_MODULES } from "./lib/module-tier.js";
28
29
  import { RECOGNIZED_MODULES } from "./parse-args.js";
@@ -183,6 +184,21 @@ const ASSET_STORE_FILES = [
183
184
  * tools/lint-architecture.ts fails on (#6649).
184
185
  */
185
186
  const WEB_AUTH_ADAPTER_IMPORT = "database";
187
+ /**
188
+ * A content payload's paths for `ModuleSlice.contentPayloadPaths` (#7133):
189
+ * each file's `${sourceRoot}/${file}` and the `${destRoot}/${file}` its
190
+ * skill's keyed fetch writes it to — read off `CONTENT_PAYLOADS`, never
191
+ * hand-listed, so the leak guards cannot drift from the manifest.
192
+ */
193
+ function payloadPaths(kind, name, destRoot) {
194
+ const payload = CONTENT_PAYLOADS.find((p) => p.kind === kind && p.name === name);
195
+ if (!payload)
196
+ throw new Error(`no content payload ${kind}/${name}`);
197
+ return payload.files.flatMap((file) => [
198
+ `${payload.sourceRoot}/${file}`,
199
+ `${destRoot}/${file}`,
200
+ ]);
201
+ }
186
202
  /**
187
203
  * The per-module slices. Authored for every `RECOGNIZED_MODULES` value; only
188
204
  * `marketing` (the default) is validated end-to-end against a golden.
@@ -1503,8 +1519,8 @@ export const MODULE_SLICES = {
1503
1519
  // module's own `pageContributions`/`starter-grid.ts` rows respectively,
1504
1520
  // so this module installs its own seeds for them directly, unlike the
1505
1521
  // sample rows above. `/referral`'s seed and flow move here the same way,
1506
- // moved out of the retired `minimalism-site` tree (spec 6892 D3), which
1507
- // owned no code of its own for either route.
1522
+ // moved out of a retired template tree (spec 6892 D3), which owned no
1523
+ // code of its own for either route.
1508
1524
  // `generate-sample-report.py` keeps its root-level destination
1509
1525
  // (finding 1 — AC7 forbids changing the Pro file set).
1510
1526
  installMap: {
@@ -1661,30 +1677,31 @@ export const MODULE_SLICES = {
1661
1677
  // it sits outside build-templates.mjs's SNAPSHOT_DIRS/overlay, so
1662
1678
  // ownedFiles/paidModuleTemplateExclusions cannot reach it) to
1663
1679
  // `module-templates/commerce/.claude/skills/scaffold-commerce/`, and
1664
- // delivered into a keyed scaffold by `installMap` instead. Ten literal
1680
+ // delivered into a keyed scaffold by `installMap` instead. Five literal
1665
1681
  // entries, not one directory entry: `extractModuleBundle`'s
1666
1682
  // `validateInstallMap` requires every source to be a FILE the bundle
1667
1683
  // produced (`extract-module-bundle.ts`'s `!sourceStat.isFile()` check) —
1668
- // a directory source is rejected outright.
1684
+ // a directory source is rejected outright. The skill ships as steps only
1685
+ // (#7133): its `templates/` and `references/` are the keyed content
1686
+ // payload `template-set/scaffold-commerce` (src/content-payloads.ts),
1687
+ // which `scripts/fetch-templates.ts` fetches with the site's licence key;
1688
+ // `link/` carries byte copies of the link path's two files, which need no
1689
+ // key.
1669
1690
  installMap: {
1670
1691
  "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/SKILL.md": ".claude/skills/scaffold-commerce/SKILL.md",
1671
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/references/lookup-keys-template.md": ".claude/skills/scaffold-commerce/references/lookup-keys-template.md",
1672
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md": ".claude/skills/scaffold-commerce/references/marketplace-brand-pages.md",
1673
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts": ".claude/skills/scaffold-commerce/templates/checkout-route.template.ts",
1674
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts": ".claude/skills/scaffold-commerce/templates/invoice-route.template.ts",
1675
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl": ".claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl",
1676
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts": ".claude/skills/scaffold-commerce/templates/payment-link-route.template.ts",
1677
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/portal-route.template.ts": ".claude/skills/scaffold-commerce/templates/portal-route.template.ts",
1678
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts": ".claude/skills/scaffold-commerce/templates/stripe-config.template.ts",
1679
- "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts": ".claude/skills/scaffold-commerce/templates/subscription-route.template.ts",
1692
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/link/marketplace-brand-pages.md": ".claude/skills/scaffold-commerce/link/marketplace-brand-pages.md",
1693
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/link/marketplace-cta-route.tsx.tmpl": ".claude/skills/scaffold-commerce/link/marketplace-cta-route.tsx.tmpl",
1694
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/scripts/fetch-templates.ts": ".claude/skills/scaffold-commerce/scripts/fetch-templates.ts",
1695
+ "packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/scripts/stripe-catalog.ts": ".claude/skills/scaffold-commerce/scripts/stripe-catalog.ts",
1680
1696
  // The commerce-boundary rule (#6039/#6070, this entry #6041): ships to
1681
1697
  // a commerce-keyed scaffold's project root so an agent working there
1682
1698
  // reads the money/live-mode/personal-data classification before it
1683
- // acts. One more literal entry alongside the ten scaffold-commerce
1699
+ // acts. One more literal entry alongside the five scaffold-commerce
1684
1700
  // skill entries above, for the same reason — a directory source is
1685
1701
  // rejected by `validateInstallMap`.
1686
1702
  "packages/create-ailk/module-templates/commerce/.claude/rules/commerce-boundary.md": ".claude/rules/commerce-boundary.md",
1687
1703
  },
1704
+ contentPayloadPaths: payloadPaths("template-set", "scaffold-commerce", ".claude/skills/scaffold-commerce"),
1688
1705
  // patches (#5581 U5, D5) — delivers the extracted component package to a
1689
1706
  // keyed scaffold's apps/web, mirroring the workspace slice's identical
1690
1707
  // merge-keys op above.
@@ -1832,12 +1849,27 @@ export function coOwnedModuleDestinations() {
1832
1849
  // see that entry, and sync-routes' AC-5 coverage test, which reads this
1833
1850
  // list so the resulting ROUTE_SINCE-vs-template absence is justified
1834
1851
  // rather than reported stale.
1852
+ // - the three app-side suites of the API ↔ MCP parity guard (#7135, spec
1853
+ // docs/specs/7135-an-api-mcp-parity-guard-spec.md D11) — file entries,
1854
+ // matched exactly by `isExcludedTemplatePath`. Each imports the guard's
1855
+ // core from the monorepo's root `tests/parity/api-parity/` by relative
1856
+ // path, which a scaffold does not carry. A test that imports a slice file
1857
+ // is a dependent, not an import-graph companion, so
1858
+ // `paidModuleTemplateExclusions` never sweeps these along.
1859
+ // - apps/mcp/__tests__/pro-tool-call-gate.test.ts (#7315, spec
1860
+ // docs/specs/7315-a-gated-pro-tool-re-spec.md D4) — same reason as the
1861
+ // three suites above: it imports `SITE_ARGS` from the monorepo's root
1862
+ // `tests/parity/` tree, which no scaffold carries.
1835
1863
  export const EXCLUDE_TEMPLATE_DIRS = [
1836
1864
  "apps/web/__tests__",
1837
1865
  "apps/web/app/__tests__",
1838
1866
  "apps/web/app/[locale]/__tests__",
1839
1867
  "apps/web/app/[locale]/(authed)/edit",
1840
1868
  "apps/web/content",
1869
+ "apps/api/src/__tests__/api-parity.test.ts",
1870
+ "apps/api/src/__tests__/api-parity-paid.test.ts",
1871
+ "apps/mcp/__tests__/api-parity.test.ts",
1872
+ "apps/mcp/__tests__/pro-tool-call-gate.test.ts",
1841
1873
  ];
1842
1874
  export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
1843
1875
  const roots = new Set(snapshotRoots);
@@ -103,6 +103,8 @@ export interface ParseEnv {
103
103
  /** A human can answer a prompt: `process.stdin.isTTY === true` in cli.ts. */
104
104
  interactive: boolean;
105
105
  }
106
+ /** The two lines that install the `ailk` plugin in a Claude Code session (#7298). */
107
+ export declare const PLUGIN_INSTALL_LINES: readonly ["/plugin marketplace add working-theory-labs/claude-plugins", "/plugin install ailk@working-theory"];
106
108
  export declare const helpText: string;
107
109
  /**
108
110
  * Parse `create-ailk` argv. `env.interactive` defaults to false —
@@ -65,6 +65,11 @@ export class ParseError extends Error {
65
65
  this.name = "ParseError";
66
66
  }
67
67
  }
68
+ /** The two lines that install the `ailk` plugin in a Claude Code session (#7298). */
69
+ export const PLUGIN_INSTALL_LINES = [
70
+ "/plugin marketplace add working-theory-labs/claude-plugins",
71
+ "/plugin install ailk@working-theory",
72
+ ];
68
73
  export const helpText = `create-ailk — scaffold a new AI Launch Kit site.
69
74
 
70
75
  Usage:
@@ -166,6 +171,8 @@ Next steps after a successful scaffold:
166
171
  cd <target-dir>
167
172
  pnpm install
168
173
  pnpm dev
174
+ Then, in Claude Code, install the ailk plugin:
175
+ ${PLUGIN_INSTALL_LINES.map((line) => ` ${line}`).join("\n")}
169
176
 
170
177
  Reference: ADR 0007 — \`pnpm create ailk@latest\` is the canonical install path.
171
178
  `;