create-ailk 0.10.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 (70) hide show
  1. package/README.md +7 -2
  2. package/dist/cli.js +5 -1
  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/module-architecture.d.ts +9 -0
  10. package/dist/module-architecture.js +43 -12
  11. package/dist/parse-args.d.ts +2 -0
  12. package/dist/parse-args.js +7 -0
  13. package/dist/surfaces.js +2 -3
  14. package/package.json +3 -3
  15. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +4 -3
  16. package/templates/.claude/settings.json +12 -0
  17. package/templates/.env.example +1 -1
  18. package/templates/CLAUDE.md +38 -22
  19. package/templates/README.md +12 -12
  20. package/templates/apps/api/jest.config.cjs +1 -0
  21. package/templates/apps/api/package.json +1 -0
  22. package/templates/apps/mcp/CLAUDE.md +23 -10
  23. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +160 -17
  24. package/templates/apps/mcp/__tests__/index.test.ts +118 -1
  25. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +425 -5
  26. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +67 -3
  27. package/templates/apps/mcp/__tests__/server.test.ts +162 -1
  28. package/templates/apps/mcp/src/index.ts +35 -3
  29. package/templates/apps/mcp/src/lib/commerce-claim.ts +193 -24
  30. package/templates/apps/mcp/src/server.ts +77 -5
  31. package/templates/apps/mcp/src/tools/purchase.ts +1 -1
  32. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +15 -0
  33. package/templates/apps/web/app/.well-known/mcp.json/route.ts +10 -3
  34. package/templates/apps/web/src/lib/commerce-claim.ts +15 -6
  35. package/templates/content/_site.mdx +23 -4
  36. package/templates/database/CHANGELOG.md +7 -0
  37. package/templates/database/package.json +1 -1
  38. package/versions.json +34 -33
  39. package/templates/.claude/skills/README.md +0 -37
  40. package/templates/.claude/skills/add-auth/SKILL.md +0 -313
  41. package/templates/.claude/skills/add-auth/references/env-vars.md +0 -106
  42. package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +0 -180
  43. package/templates/.claude/skills/add-page/SKILL.md +0 -260
  44. package/templates/.claude/skills/add-page/references/page-types.md +0 -123
  45. package/templates/.claude/skills/add-page/scripts/site-pages.mjs +0 -87
  46. package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +0 -99
  47. package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +0 -18
  48. package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +0 -43
  49. package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +0 -52
  50. package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +0 -38
  51. package/templates/.claude/skills/check-site/SKILL.md +0 -245
  52. package/templates/.claude/skills/check-site/references/common-errors.md +0 -116
  53. package/templates/.claude/skills/check-site/references/findings-glossary.md +0 -140
  54. package/templates/.claude/skills/check-site/references/severity-levels.md +0 -114
  55. package/templates/.claude/skills/fill-page/SKILL.md +0 -199
  56. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +0 -80
  57. package/templates/.claude/skills/fill-page/references/section-sources.md +0 -41
  58. package/templates/.claude/skills/setup-database/SKILL.md +0 -156
  59. package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +0 -154
  60. package/templates/.claude/skills/setup-deploy/SKILL.md +0 -312
  61. package/templates/.claude/skills/setup-deploy/references/runbook-template.md +0 -204
  62. package/templates/.claude/skills/setup-deploy/references/vercel-json-template.md +0 -98
  63. package/templates/.claude/skills/update-page/SKILL.md +0 -117
  64. package/templates/.claude/skills/update-page/scripts/update-page.ts +0 -640
  65. package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +0 -31
  66. package/templates/.claude/skills/upgrade-plan/SKILL.md +0 -121
  67. package/templates/.claude/skills/upgrade-plan/references/options.md +0 -24
  68. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +0 -527
  69. package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +0 -75
  70. package/templates/apps/mcp/src/lib/require-paid-tier.ts +0 -44
@@ -1,106 +0,0 @@
1
- # Auth Env-Var Contract
2
-
3
- Source of truth for which env vars activate which auth provider on
4
- an AILK consumer site. Read by the `add-auth` skill before
5
- asking the consumer to paste secrets.
6
-
7
- The activation logic lives in `apps/web/lib/auth-env.ts` →
8
- `getActiveProviderIds()`. A provider is active **only when every
9
- required var is set to a non-empty value**. Partial wiring (e.g.,
10
- `GOOGLE_CLIENT_ID` set but `GOOGLE_CLIENT_SECRET` missing) is treated
11
- as inactive — `apps/web/auth.ts` never constructs that provider.
12
-
13
- The site's auth library is **Better Auth** (`apps/web/auth.ts`,
14
- `betterAuth({ secret, baseURL, socialProviders, … })`).
15
-
16
- ## Provider activation table
17
-
18
- | Provider ID | Required env vars | Source |
19
- | ----------- | -------------------------------------------- | ---------------------------- |
20
- | `email` | `AUTH_EMAIL_SERVER` + `AUTH_EMAIL_FROM` | Email sign-in (Resend)¹ |
21
- | `google` | `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` | Google OAuth |
22
- | `github` | `GITHUB_CLIENT_ID` + `GITHUB_CLIENT_SECRET` | GitHub OAuth |
23
-
24
- ¹ Better Auth core ships no magic-link provider, so `email` is an
25
- env-presence signal: it switches on the password method
26
- (`emailAndPassword`) in `apps/web/auth.ts`, not a wired magic link.
27
-
28
- ## Auxiliary auth env vars
29
-
30
- | Var | Default | Effect |
31
- | ---------------------- | ------- | ------------------------------------------------------------------------------------- |
32
- | `AILK_AUTH_REQUIRED` | unset | Read by `isAuthRequired()` in `apps/web/lib/auth-env.ts` (`"1"` or `"true"`). Default is auth-disabled / public, so a fresh scaffold boots without requiring any auth wiring. |
33
- | `BETTER_AUTH_URL` | unset | The site's public origin (e.g. `https://your-domain.com`). Better Auth builds OAuth callback URLs on it and it is the one trusted origin; unset, Better Auth derives both from each request. Set it in production. Local dev can leave it unset. |
34
- | `BETTER_AUTH_SECRET` | unset | Signs session cookies and email-verification tokens. **Required in production** — `apps/web/auth.ts` throws when it is unset there, rather than fall back to the development secret published in the source. Generate with `openssl rand -base64 32`. Optional for `next dev`. |
35
- | `AILK_AUTH_SIGNUP_OPEN` | unset | Read by `isSignUpOpen()` in `apps/web/lib/auth-env.ts` (`"1"` or `"true"`). Unset, public sign-up is closed: with `email` active, the password form signs existing accounts in but creates none. |
36
- | `AILK_AUTH_PASSWORD_RESET` | unset | Read by `isResetAvailable()` (`"1"` or `"true"`). Password reset also needs `BETTER_AUTH_URL`, `BETTER_AUTH_SECRET` and `AUTH_EMAIL_WEBHOOK_SECRET` (`resetSendConfigured()`). |
37
- | `AUTH_EMAIL_WEBHOOK_SECRET` | unset | Signs the auth-mail relay from `apps/web` to `apps/api`, which sends with `RESEND_API_KEY` from `AUTH_EMAIL_FROM`. Set the same value for both apps; unset, no auth mail is sent. |
38
-
39
- The skill writes none of these auxiliary vars unasked. It mentions
40
- them in the closing summary so the consumer is aware:
41
-
42
- - `AILK_AUTH_REQUIRED` is the consumer's call (skill always defaults
43
- to leaving it unset).
44
- - `AILK_AUTH_SIGNUP_OPEN` is the consumer's call too; the skill asks
45
- when `email` is active, because with sign-up closed and no OAuth
46
- provider nobody can create the first account.
47
- - `AILK_AUTH_PASSWORD_RESET` and `AUTH_EMAIL_WEBHOOK_SECRET` are the
48
- auth-mail setup the `email` sub-flow describes.
49
- - `BETTER_AUTH_URL` and `BETTER_AUTH_SECRET` are deferred to
50
- production-time setup (`setup-deploy`), and a production deploy
51
- fails without `BETTER_AUTH_SECRET`.
52
-
53
- ## Env-var formats — what valid values look like
54
-
55
- ### `AUTH_EMAIL_SERVER` (email sign-in via Resend)
56
-
57
- ```
58
- smtp://resend:<RESEND_API_KEY>@smtp.resend.com:465
59
- ```
60
-
61
- The Resend API key starts with `re_`. The literal username is the
62
- string `resend` (Resend's SMTP convention). Port 465 is implicit-TLS
63
- SMTP.
64
-
65
- ### `AUTH_EMAIL_FROM` (email sign-in via Resend)
66
-
67
- A verified sender address on the consumer's Resend account. For
68
- development, `onboarding@resend.dev` works without verification.
69
-
70
- ### `GOOGLE_CLIENT_ID` / `GITHUB_CLIENT_ID`
71
-
72
- Public identifiers issued by Google / GitHub. Typically:
73
-
74
- - Google: ends in `.apps.googleusercontent.com`
75
- - GitHub: 20-character hex string
76
-
77
- Either can be safely committed to a public template; production
78
- secrets stay in `.env.local`.
79
-
80
- ### `GOOGLE_CLIENT_SECRET` / `GITHUB_CLIENT_SECRET`
81
-
82
- Sensitive values. Treat as passwords. Never commit. The skill writes
83
- these only to `.env.local` (which is in the consumer scaffold's
84
- `.gitignore` by default).
85
-
86
- ## Detection of "already activated"
87
-
88
- The skill's Step 1 menu marks providers as `[x]` when both required
89
- vars are set in `.env.local`. The detection is presence-only — the
90
- skill does not validate that the values actually work (no Google
91
- API ping, no GitHub API ping, no test SMTP connection). A consumer
92
- who pasted typos will get a runtime auth error from Better Auth on
93
- their first sign-in attempt; the fix is to re-run the skill and
94
- overwrite.
95
-
96
- ## Where these vars are read
97
-
98
- - `apps/web/lib/auth-env.ts` — `getActiveProviderIds()` and
99
- `isAuthRequired()`.
100
- - `apps/web/auth.ts` — `buildSocialProviders()` consumes the
101
- active-provider list and builds Better Auth's `socialProviders`
102
- from the matching env vars; `resolveSecret()` reads
103
- `BETTER_AUTH_SECRET`, and `baseURL` is `BETTER_AUTH_URL`.
104
-
105
- The skill never modifies any of those files. All activation is
106
- env-var-driven.
@@ -1,180 +0,0 @@
1
- # OAuth + Email Sign-In Setup — RUNBOOK Template
2
-
3
- The `add-auth` skill writes (or merges into) `RUNBOOK.md` at the
4
- consumer's repo root using the per-provider sections below. Only the
5
- sections for providers actually activated this session are emitted.
6
-
7
- The skill **substitutes nothing** in these blocks at runtime — they're
8
- copy-paste ready as-is. The only per-session variation is which
9
- sections appear in the final runbook.
10
-
11
- ---
12
-
13
- ## Provider section: email (Resend)
14
-
15
- ```markdown
16
- ## Auth — email sign-in (Resend)
17
-
18
- Email-and-password sign-in is activated when both env vars are present
19
- in `.env.local`:
20
-
21
- - `AUTH_EMAIL_SERVER` — `smtp://resend:<API_KEY>@smtp.resend.com:465`
22
- - `AUTH_EMAIL_FROM` — verified sender address on your Resend account
23
-
24
- ### One-time setup
25
-
26
- 1. Sign up at <https://resend.com> (free tier is fine for development).
27
- 2. Create an API key: <https://resend.com/api-keys>. Save it
28
- somewhere safe — you'll only see it once.
29
- 3. Use a verified sender. For development, `onboarding@resend.dev` is
30
- Resend's shared dev sender and works without verification. For
31
- production, verify your own sending domain in the Resend dashboard.
32
-
33
- `AUTH_EMAIL_SERVER` is read for its presence only. The auth mail
34
- (verification, password reset) is sent by `apps/api` with
35
- `RESEND_API_KEY` from `AUTH_EMAIL_FROM`, so set both for the API, and
36
- set `AUTH_EMAIL_WEBHOOK_SECRET` to the same random value for both apps.
37
-
38
- Public sign-up stays closed until you set `AILK_AUTH_SIGNUP_OPEN=1` in
39
- `.env.local`.
40
-
41
- ### `.env.local` entries
42
-
43
- \`\`\`
44
- AUTH_EMAIL_SERVER=smtp://resend:re_yourapikeyhere@smtp.resend.com:465
45
- AUTH_EMAIL_FROM=onboarding@resend.dev
46
- \`\`\`
47
-
48
- ### Rotating the API key
49
-
50
- If you rotate the Resend API key, update `AUTH_EMAIL_SERVER` in
51
- `.env.local` and restart the dev server.
52
- ```
53
-
54
- ---
55
-
56
- ## Provider section: Google OAuth
57
-
58
- ```markdown
59
- ## Auth — Google OAuth
60
-
61
- Google sign-in is activated when both env vars are present in
62
- `.env.local`:
63
-
64
- - `GOOGLE_CLIENT_ID`
65
- - `GOOGLE_CLIENT_SECRET`
66
-
67
- ### One-time setup
68
-
69
- 1. Open <https://console.cloud.google.com/apis/credentials>.
70
- 2. If you haven't yet, configure the OAuth consent screen:
71
- - User type: **External** (unless you're inside a Workspace org).
72
- - Fill in app name, support email, and developer contact email.
73
- - Scopes are requested by Better Auth at runtime — leave the
74
- consent screen scope list empty.
75
- 3. Click **Create Credentials → OAuth client ID**.
76
- 4. Application type: **Web application**.
77
- 5. Name: anything (e.g., "AILK dev").
78
- 6. **Authorized redirect URIs** — add exactly:
79
- `http://localhost:3000/api/auth/callback/google`
80
- 7. Click **Create**. Copy the Client ID and Client secret from the
81
- dialog (you can return to copy them later via the Credentials page).
82
-
83
- ### `.env.local` entries
84
-
85
- \`\`\`
86
- GOOGLE_CLIENT_ID=...
87
- GOOGLE_CLIENT_SECRET=...
88
- \`\`\`
89
-
90
- ### Production callback URL
91
-
92
- The `localhost` callback above is dev-only. For production, you'll
93
- need to add a second redirect URI to the same OAuth client (or
94
- create a separate prod client) of the form
95
- `https://<your-prod-domain>/api/auth/callback/google`. The
96
- `setup-deploy` skill or your future deploy skill will help you
97
- wire this when you're ready to ship.
98
-
99
- ### Rotating the client secret
100
-
101
- In the Google Console, find the OAuth client, generate a new secret,
102
- update `GOOGLE_CLIENT_SECRET` in `.env.local`, and restart the dev
103
- server. The Client ID stays the same.
104
- ```
105
-
106
- ---
107
-
108
- ## Provider section: GitHub OAuth
109
-
110
- ```markdown
111
- ## Auth — GitHub OAuth
112
-
113
- GitHub sign-in is activated when both env vars are present in
114
- `.env.local`:
115
-
116
- - `GITHUB_CLIENT_ID`
117
- - `GITHUB_CLIENT_SECRET`
118
-
119
- ### One-time setup
120
-
121
- 1. Open <https://github.com/settings/developers>.
122
- 2. Click **New OAuth App**.
123
- 3. Application name: anything (e.g., "AILK dev").
124
- 4. Homepage URL: `http://localhost:3000`
125
- 5. **Authorization callback URL** — add exactly:
126
- `http://localhost:3000/api/auth/callback/github`
127
- 6. Click **Register application**.
128
- 7. On the resulting app page, click **Generate a new client secret**
129
- and copy it immediately (GitHub shows it once).
130
- 8. Copy the Client ID from the same page.
131
-
132
- ### `.env.local` entries
133
-
134
- \`\`\`
135
- GITHUB_CLIENT_ID=...
136
- GITHUB_CLIENT_SECRET=...
137
- \`\`\`
138
-
139
- ### Production callback URL
140
-
141
- The `localhost` callback above is dev-only. For production, either
142
- register a separate OAuth App with a `https://<your-prod-domain>/...`
143
- callback, or update this app's callback (GitHub allows multiple).
144
- The `setup-deploy` skill or your future deploy skill will help
145
- you wire this when you're ready to ship.
146
-
147
- ### Rotating the client secret
148
-
149
- Generate a new client secret on the app's GitHub page, update
150
- `GITHUB_CLIENT_SECRET` in `.env.local`, and restart the dev server.
151
- The Client ID stays the same.
152
- ```
153
-
154
- ---
155
-
156
- ## Closing section (always emitted at the end of the runbook)
157
-
158
- ```markdown
159
- ## Activation summary
160
-
161
- | Provider | Env vars | Active |
162
- | ----------- | ------------------------------------------------- | ------ |
163
- | email | `AUTH_EMAIL_SERVER` + `AUTH_EMAIL_FROM` | <fill> |
164
- | Google | `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` | <fill> |
165
- | GitHub | `GITHUB_CLIENT_ID` + `GITHUB_CLIENT_SECRET` | <fill> |
166
-
167
- A provider is **active** when both required env vars are set in
168
- `.env.local`. The provider list is computed at server startup from env
169
- vars only (`getActiveProviderIds()` in `apps/web/lib/auth-env.ts`) — there
170
- are no `apps/web/auth.ts` edits to make.
171
-
172
- To require sign-in for protected routes, set in `.env.local`:
173
-
174
- \`\`\`
175
- AILK_AUTH_REQUIRED=1
176
- \`\`\`
177
-
178
- Default (unset or `0`) is auth-disabled / public — useful while
179
- building.
180
- ```
@@ -1,260 +0,0 @@
1
- ---
2
- name: add-page
3
- description: Add a new page to this AI Launch Kit site — a new service, a new "who it's for" page, a new booking page, a comparison against a competitor, a stand-alone landing page, or a whole route the site doesn't carry yet like /pricing — with its content file, its structured data and its nav link, in one confirm-before-write pass. Use this whenever the user wants a page that doesn't exist yet, even if they don't say "page" — "add a service for kitchen remodels", "I need a /pricing page", "make a page for dentists", "add a comparison with Acme", "we need a booking page for on-site estimates", "add a landing page for the spring offer", or a structured triple like "pricing — Pricing — plans and FAQ". Not for changing a page that already exists (fill-page writes content; update-page renames).
4
- ---
5
-
6
- # add-page
7
-
8
- Add a page this site can actually serve, at the path its routes really use,
9
- with a nav link, and nothing else. One plan, one confirmation, then write.
10
-
11
- The single most important fact about this site: **almost every page type
12
- already has its route.** Routes live in `apps/web/app/[locale]/…` and render
13
- whatever content file sits in `content/en/<Type>/`. So adding a page is
14
- usually **adding one content file**, not writing route code. Route code is
15
- only ever added by the `create-ailk add-route` command, never by hand — a
16
- hand-written route drifts from the template the site is built from and
17
- breaks the next `create-ailk` upgrade.
18
-
19
- ## Step 1 — Read what this site can serve
20
-
21
- Run, from the site's root:
22
-
23
- ```bash
24
- node .claude/skills/add-page/scripts/site-pages.mjs
25
- ```
26
-
27
- It prints the site's shape, design and level (from `.ailk-site-manifest.json`),
28
- then one row per page type this site renders: its route, whether it takes new
29
- pages (a route with a `[slug]` does; a fixed route like `/about` is one page),
30
- and the pages already written. Add `--json` if you want to parse it.
31
-
32
- Treat this output as the truth about the site. `references/page-types.md`
33
- lists every page type AI Launch Kit knows — the vocabulary — but many of them
34
- are not on this site. Never offer a page type the script doesn't list unless
35
- you are also going to add its route (Step 3, case D).
36
-
37
- Then read `.ailk-site-manifest.json` at the site root yourself, the site's
38
- roster of record:
39
-
40
- - `modules` — the modules this site has. Add any module the `# Modules:`
41
- line of `architecture.yaml` names that `modules` does not: `create-ailk
42
- add-module` records a module it adds there. With no manifest, or none
43
- carrying `modules`, that line is the set.
44
- - `rows` — every route the site already has (`route`) and the page type
45
- each renders (`pageType`).
46
- - `level` — present only when the scaffold derived it from the licence key.
47
- It never decides what to offer; `modules` does.
48
-
49
- A page type renders on this site only when the **Module** column of
50
- `references/page-types.md` names a module in that set. Never offer,
51
- suggest or write any other page type: a type whose Module the site lacks is
52
- case E, never case D.
53
-
54
- ## Step 2 — Work out which page type the user wants
55
-
56
- The user may give a structured triple (`<slug> — <Type> — <purpose>`) or plain
57
- words. Resolve plain words to a type with `references/page-types.md`: an exact
58
- slug match first (`pricing` → `Pricing`), then a prefix (`services/…` →
59
- `Service`), then the purpose ("company story" → `AboutPage`). If two types
60
- fit, or none does, ask — a wrong type gives the page the wrong structured
61
- data, which is what search and answer engines read.
62
-
63
- Also settle the slug: lowercase, hyphenated, from the user's words
64
- (`kitchen-remodels`, not `kitchen_remodels` or `KitchenRemodels`).
65
-
66
- ## Step 3 — Decide which case this is
67
-
68
- Check case C first: if the page the user describes already exists (the type is on a fixed route and its one page is in the "existing" column, or the slug is taken), stop there — nothing else in this skill applies.
69
-
70
- | Case | When | What you'll write |
71
- |---|---|---|
72
- | **A. New page on an existing route** | The type is listed and takes new pages (`Service`, `UseCase`, `PortfolioItem`, `BookCall`, `ThankYouPage`, …) | One content file: `content/en/<Type>/<slug>.mdx`. Plus a nav link if the page belongs in the nav. |
73
- | **B. Stand-alone landing page** | A one-off page at `/<slug>` that isn't any specific type (an offer, a campaign) | One content file: `content/en/LandingPage/<slug>.mdx`. The site's catch-all route serves it. |
74
- | **C. The page already exists** | A fixed-route type already has its page (`/about`, `/contact`, `/faq`), or `<slug>` is taken | Nothing. Say so. To change its words, use `fill-page`; to rename it or move its URL, use `update-page`. |
75
- | **D. The route isn't on this site** | The type exists in `page-types.md` but not in the script's output (e.g. `/pricing` on a person-service site) | First the route: `pnpm create ailk@latest add-route <route-id>` (run in the site root). `<route-id>` is the route family's name as `pnpm create ailk@latest --help` lists it under `--surfaces` — usually the first path segment (`pricing`, `alternatives`, `careers`). Then continue as case A (or case C for a fixed route — `add-route` seeds its page). |
76
- | **E. Needs a module the site doesn't have** | The type's **Module** in `page-types.md` is not in the site's modules (Step 1), or `add-route` refuses, naming a module (e.g. docs, blog, scheduling-backed booking) | Nothing yet. Tell the user which module renders it and which level carries it (the **Level** column), and point at the `upgrade-plan` skill. Don't try to recreate the module's routes by hand. |
77
-
78
- Why case B uses `LandingPage` rather than a new route: a route directory is
79
- template code the site inherits and upgrades; a content file is the owner's.
80
- Keeping one-off pages as content keeps upgrades clean.
81
-
82
- Comparison pages (`/vs/<competitor>`, listed on `/alternatives`) are case A on
83
- the `alternatives` route family, or case D when the site doesn't carry it yet
84
- (`add-route alternatives`). The `/alternatives` hub lists every comparison
85
- automatically — never hand-list them on the hub's page.
86
-
87
- Reports and flow landing pages are content on routes the **signal-collectors
88
- module** owns — see "Reports" and "Flow landing pages" below.
89
-
90
- ## Step 4 — Draft the content file
91
-
92
- Copy the shape of a page the site already has **of the same type** — pick one
93
- from the "existing" column (e.g. `content/en/Service/first-engagement.mdx`).
94
- Its frontmatter is exactly what that type's route and structured-data builder
95
- expect, so matching it is how the new page renders and emits valid JSON-LD.
96
- Keep the same keys and section structure; replace the words.
97
-
98
- - Pick the page to copy with care: if the only page of this type is an odd one
99
- (a hub slug, a stub), start from `templates/content.mdx.tmpl` instead and
100
- borrow the section shapes from `content/en/HomePage/home.mdx`.
101
- - Set `slug` to the new slug and write a real `title` and `description` from
102
- what the user said. `description` becomes the meta description and feeds the
103
- structured data, so make it a true one-sentence summary.
104
- - Use the site's own name from `content/_site.mdx` wherever the page names
105
- the business (a comparison's "us" side, a CTA) — don't leave `{Your Name}`
106
- when the answer is on disk.
107
- - A time-limited offer carries its end date in structured data
108
- (`schema: { offers: { validThrough: "2027-05-31" } }`) and says it in the
109
- copy, so the page doesn't keep advertising an expired deal.
110
- - Where you don't have the owner's facts (prices, names, results), write
111
- clearly-marked placeholder copy — never invent facts about their business.
112
- Offer `fill-page` afterwards to write it properly.
113
- - If no page of this type exists yet, start from `templates/content.mdx.tmpl`
114
- (or `templates/comparison-page.mdx.tmpl` for a comparison) and fill in the
115
- `type` from `page-types.md`. Reports and flow landing pages have their own
116
- templates — see their sections below.
117
- - Extra structured data (an `Offer`, a `Review`, an `author`) goes in the
118
- frontmatter `schema:` block — see "Adding schema to a page" below. Don't
119
- hand-write JSON-LD anywhere.
120
-
121
- ## Step 5 — Link the page from its hub and the nav
122
-
123
- **The hub page.** Detail types sit under a list page: `Service` under
124
- `ServicesList` (`/services`), `UseCase` under `UseCasesList` (`/for`),
125
- `PortfolioItem` under `PortfolioList` (`/portfolio`). Open the hub's content
126
- file. If it lists its entries by hand (an `items` section with one entry per
127
- page, each with an `href`), add an entry for the new page in the same shape —
128
- otherwise visitors can't find it. The comparisons hub (`/alternatives`) and the
129
- reports hub (`/reports`) are the exceptions: they list their pages automatically.
130
-
131
- **The nav.**
132
-
133
- The site nav is `apps/web/lib/nav-config.ts`; its labels are keys in the
134
- `nav` section of `apps/web/messages/<locale>.json` (one file per language
135
- under `apps/web/messages/`).
136
-
137
- - A detail page under a list (a new service, a new persona) usually belongs in
138
- that list's flyout or group in `nav-config.ts`. A stand-alone landing page
139
- usually doesn't belong in the nav at all — ask if unsure. If it isn't in the
140
- nav or a hub, tell the user how visitors will reach it (a link from the home
141
- page, an email, an ad), because nothing on the site points to it yet.
142
- - Name a new `labelKey` after the page in camelCase (`kitchenRemodels`), not
143
- after its position (`service4`), so it still makes sense after reordering.
144
- - A flyout may list the same links twice — once in `features` (the compact
145
- view) and once in `groups` (the grouped view). Add the link to both.
146
- - To add a link: add an entry with the new `labelKey` and the locale-free path
147
- (`/services/kitchen-remodels`, not `/en/…`), and add that key with its label
148
- to **every** messages file (a site with one language has one file). A key
149
- missing from one language breaks that language's nav.
150
- - Know the trade-off and tell the user once: `create-ailk` regenerates the nav
151
- when a route or module is added, but only while `nav-config.ts` is exactly as
152
- generated. Once hand-edited, later `add-route`/`add-module` runs leave it
153
- alone and print the change they wanted instead.
154
-
155
- ## Step 6 — Show the plan, confirm once, write
156
-
157
- Show every file you will create or change — the content file in full, and the
158
- exact lines added to the hub page, `nav-config.ts` and each messages file — plus any
159
- `add-route` command you'll run first. Wait for one confirmation. Then run the
160
- command (if any) and write the files. Don't write anything before the user
161
- confirms, and don't touch any other file.
162
-
163
- ## Step 7 — Check it
164
-
165
- Run `pnpm build` from the site root. Then tell the user:
166
-
167
- - the new page's URL (with the locale, e.g. `/en/services/kitchen-remodels`)
168
- - that `fill-page` can write its real copy, and `check-site` can score it
169
- for search and answer-engine readiness.
170
-
171
- If the build fails, the usual cause is frontmatter that doesn't match the
172
- type — compare the new file with the page you copied in Step 4.
173
-
174
- ## Reports
175
-
176
- `ReportPage` (the `/reports/<slug>` report reader) is a hub-and-spoke family
177
- listed on the `/reports` hub (`ReportsList`). Its routes are
178
- **module-owned code** (the signal-collectors module's own
179
- `app/[locale]/reports/[slug]/page.tsx` and `app/[locale]/reports/page.tsx`),
180
- so this skill writes report **content only**, never report route code
181
- (spec 6892 D5).
182
-
183
- When the resolved type is `ReportPage` — a structured triple naming it, or
184
- free text like "add a report on Q3 results" — do this instead of Steps 3–4:
185
-
186
- 1. Refuse when `app/[locale]/reports/[slug]/page.tsx` is absent, naming the
187
- `signal-collectors` module: "This site has no report reader yet — run
188
- `create-ailk add-module signal-collectors` first."
189
- 2. Write `content/en/ReportPage/<slug>.mdx` from
190
- `templates/report-page.mdx.tmpl` (substituting `{{SLUG}}`, `{{TITLE}}`,
191
- `{{DESCRIPTION}}`, `{{DATE_PUBLISHED}}` — an ISO date — and
192
- `{{DATE_LABEL}}` — the display string, e.g. "March 2025"). Refuse only
193
- when that file already exists.
194
- 3. When `content/en/ReportsList/reports.mdx` is absent, write it from
195
- `templates/reports-list.mdx.tmpl` (substituting `{{TITLE}}`/`{{DESCRIPTION}}`
196
- for the hub's own copy).
197
-
198
- The hub's own card list is **generated at request time** from whichever
199
- `ReportPage` seeds exist (`app/[locale]/reports/page.tsx` injects them) —
200
- never hand-list reports on the hub's MDX. Steps 5–7 apply as usual.
201
-
202
- ## Flow landing pages
203
-
204
- A flow landing page is a single, standalone `LandingPage` whose CTA opens a
205
- named flow at `/flows/<slug>/start` — the referral-request shape (spec 6892
206
- D3). It belongs to no hub, but like the report family its route is
207
- **module-owned code** (the signal-collectors module's own
208
- `app/[locale]/flows/[slug]/start/page.tsx`), so this skill writes content
209
- only.
210
-
211
- When the consumer asks for a flow landing page — "add a referral page",
212
- "add a landing page for the intake flow" — given a flow slug:
213
-
214
- 1. Refuse when `app/[locale]/flows/[slug]/start/page.tsx` is absent, naming
215
- the `signal-collectors` module: "This site has no flow engine yet — run
216
- `create-ailk add-module signal-collectors` first."
217
- 2. Refuse when `content/en/LandingPage/<slug>.mdx` already exists — this
218
- is a single standalone page, not a family member with its own detail
219
- route.
220
- 3. Write `content/en/LandingPage/<slug>.mdx` from
221
- `templates/flow-landing-page.mdx.tmpl`, substituting `{{SLUG}}`,
222
- `{{TITLE}}`, `{{DESCRIPTION}}` as usual and `{{FLOW_SLUG}}` — the flow's
223
- own slug, which may differ from the page's `{{SLUG}}`.
224
-
225
- This skill writes no flow definition (`database/flows/<slug>.flow.json`) —
226
- that is a separate artifact, seeded through the flow engine, not through a
227
- page.
228
-
229
- ## Adding schema to a page
230
-
231
- Every page's frontmatter has an escape hatch, `schema:`, that the page's
232
- structured-data builder merges on top of what it derives (frontmatter wins).
233
- Use it to add sub-entities or enrich fields, on a new page or an existing one:
234
-
235
- ```yaml
236
- ---
237
- type: Service
238
- slug: kitchen-remodels
239
- title: Kitchen remodels
240
- description: Full kitchen remodels, from design to final inspection.
241
- schema:
242
- offers:
243
- "@type": Offer
244
- priceCurrency: USD
245
- price: "15000"
246
- ---
247
- ```
248
-
249
- On types that wrap a described entity in a page (`Service`, `Product`,
250
- `BlogPosting`, `JobPosting`), most keys land on the entity; on others they
251
- merge at the top level. This is also how a page gets a supplementary type
252
- with no page type of its own — `Offer`, `Review`, `AggregateRating`.
253
-
254
- ## What this skill does not do
255
-
256
- - Write or copy route code by hand — `create-ailk add-route` does that.
257
- - Change an existing page's words (`fill-page`), name or URL (`update-page`).
258
- - Add a module or change the site's level (`upgrade-plan` explains; the
259
- owner decides).
260
- - Edit `_site.mdx`, the design, or another page's content.
@@ -1,123 +0,0 @@
1
- <!-- GENERATED — do not hand-edit. AI Launch Kit generates this file
2
- from its page-type registry and ships it with the scaffold. The
3
- source of record for this site's own page-type registry is `pageTypeRegistry` in the installed `@working-theory/schema` package.
4
- -->
5
-
6
- # Page-Type → schema.org Registry Reference
7
-
8
- The full page-type catalog the `add-page` skill reads at runtime to
9
- resolve a slug or page-purpose hint to a family, chassis, schema.org
10
- `@type`, and route convention. Generated from the page-type registry
11
- `@working-theory/schema` exports — every registered page type appears
12
- exactly once, list/detail pairs are shown together, and no row here
13
- can name a type the registry doesn't have.
14
-
15
- The skill resolves in this order:
16
-
17
- 1. **Exact slug match** — `pricing` → `Pricing`.
18
- 2. **Slug-prefix match** — `blog/<anything>` → `BlogPosting`.
19
- 3. **Free-text hint match** — "company story" / "team" → `AboutPage`.
20
- 4. **Ask** — if nothing matches, ask the consumer rather than guessing.
21
-
22
- **Level** and **Module** name what a page type needs: the module whose
23
- code renders it, and the level that module comes with. Read the site's
24
- `.ailk-site-manifest.json` `modules` first — a page type whose Module is
25
- not in that list is one this site cannot render, so it is never offered;
26
- name its module and level instead (the `upgrade-plan` skill explains the
27
- levels). The level is the module's, never a claim about this site's key.
28
-
29
- `<slug>` in a route below is the page's own slug (e.g. `hello-world`);
30
- `<Type>` families using two-layer builders route supplementary schema
31
- through the `schema:` frontmatter override — see this skill's
32
- [Adding schema to an existing page](../SKILL.md#adding-schema-to-an-existing-page) section, or the installed `@working-theory/schema`
33
- package's README for the full override contract.
34
-
35
- ## Core
36
-
37
- | Key | `@type` | Family | Chassis | Route | Level | Module |
38
- | --- | --- | --- | --- | --- | --- | --- |
39
- | `HomePage` | `HomePage` | core | home | / | Free | `marketing` |
40
- | `AboutPage` | `AboutPage` | core | detail | /about | Free | `marketing` |
41
- | `ContactPage` | `ContactPage` | core | detail | /contact | Free | `marketing` |
42
- | `PrivacyPage` | `WebPage` | core | detail | /privacy | Free | `marketing` |
43
- | `TermsPage` | `WebPage` | core | detail | /terms | Free | `marketing` |
44
- | `PurchasePolicyPage` | `WebPage` | core | detail | /purchase-policy | Free | `marketing` |
45
- | `WorkingAgreementPage` | `WebPage` | core | detail | /working-agreement | Free | `marketing` |
46
- | `LandingPage` | `WebPage` | core | home | /<slug> | Free | `marketing` |
47
- | `ThankYouPage` | `WebPage` | core | detail | /thanks/<slug> | Free | `marketing` |
48
- | `BookCall` | `WebPage` | core | home | /meet/:slug | Pro | `scheduling` |
49
- | `NewsletterSignup` | `WebPage` | core | home | /newsletter | Free | `marketing` |
50
- | `DemoRequest` | `WebPage` | core | home | /demo-request | Free | `marketing` |
51
- | `TrialSignup` | `WebPage` | core | home | /trial-signup | Free | `marketing` |
52
- | `Waitlist` | `WebPage` | core | home | /waitlist | Free | `marketing` |
53
- | `Assessment` | `WebApplication` | core | home | /assessment | Pro | `signal-collectors` |
54
- | `Calculator` | `WebApplication` | core | home | /calculator | Pro | `signal-collectors` |
55
- | `CampaignLP` | `WebPage` | core | home | /<slug> | Free | `marketing` |
56
-
57
- ## Identity
58
-
59
- | Key | `@type` | Family | Chassis | Route | Level | Module |
60
- | --- | --- | --- | --- | --- | --- | --- |
61
- | `Organization` | `Organization` | identity | detail | — site-wide identity — emitted on the home page or in a layout | Free | `marketing` |
62
- | `Person` | `Person` | identity | detail | — site-wide identity — emitted on the home page or in a layout | Free | `marketing` |
63
- | `BreadcrumbList` | `BreadcrumbList` | identity | detail | — site-wide — attached alongside another page's own schema | Free | `marketing` |
64
- | `ContactPoint` | `ContactPoint` | identity | detail | — site-wide — embedded in `Organization` or `Person` | Free | `marketing` |
65
-
66
- ## Canonical
67
-
68
- | Key | `@type` | Family | Chassis | Route | Level | Module |
69
- | --- | --- | --- | --- | --- | --- | --- |
70
- | `ServicesList` | `CollectionPage` | canonical-list | list | /services | Free | `marketing` |
71
- | `Service` | `Service` | canonical-detail | detail | /services/<slug> | Free | `marketing` |
72
- | `ProductsList` | `CollectionPage` | canonical-list | list | /products | Free | `marketing` |
73
- | `Product` | `Product` | canonical-detail | detail | /products/<slug> | Free | `marketing` |
74
- | `ProjectsList` | `CollectionPage` | canonical-list | list | /projects | Free | `marketing` |
75
- | `Project` | `CreativeWork` | canonical-detail | detail | /projects/<slug> | Free | `marketing` |
76
- | `FAQPage` | `FAQPage` | canonical-list | list | /faq | Free | `marketing` |
77
- | `CareersList` | `CollectionPage` | canonical-list | list | /careers | Free | `marketing` |
78
- | `JobPosting` | `JobPosting` | canonical-detail | detail | /careers/<slug> | Free | `marketing` |
79
- | `UseCasesList` | `CollectionPage` | canonical-list | list | /for | Free | `marketing` |
80
- | `UseCase` | `WebPage` | canonical-detail | detail | /for/<slug> | Free | `marketing` |
81
- | `PortfolioList` | `CollectionPage` | canonical-list | list | /portfolio | Free | `marketing` |
82
- | `PortfolioItem` | `WebPage` | canonical-detail | detail | /portfolio/<slug> | Free | `marketing` |
83
- | `FeaturesList` | `CollectionPage` | canonical-list | list | /features | Free | `marketing` |
84
- | `Feature` | `WebPage` | canonical-detail | detail | /features/<slug> | Free | `marketing` |
85
- | `ComparisonsList` | `CollectionPage` | canonical-list | list | /alternatives | Free | `marketing` |
86
- | `ComparisonPage` | `WebPage` | canonical-detail | detail | /vs/<slug> | Free | `marketing` |
87
- | `ReportsList` | `CollectionPage` | canonical-list | list | /reports | Free | `marketing` |
88
- | `ReportPage` | `Report` | canonical-detail | detail | /reports/<slug> | Pro | `signal-collectors` |
89
- | `TestimonialsList` | `CollectionPage` | canonical-list | list | /testimonials | Free | `marketing` |
90
- | `ClientsList` | `CollectionPage` | canonical-list | list | /clients | Free | `marketing` |
91
- | `DownloadsList` | `CollectionPage` | canonical-list | list | /downloads | Free | `marketing` |
92
- | `EventsList` | `CollectionPage` | canonical-list | list | /events | Free | `marketing` |
93
- | `Event` | `Event` | canonical-detail | detail | /events/<slug> | Free | `marketing` |
94
- | `CoursesList` | `CollectionPage` | canonical-list | list | /courses | Free | `marketing` |
95
- | `Course` | `Course` | canonical-detail | detail | /courses/<slug> | Free | `marketing` |
96
- | `TeamList` | `CollectionPage` | canonical-list | list | /team | Free | `marketing` |
97
- | `TeamMember` | `ProfilePage` | canonical-detail | detail | /team/<slug> | Free | `marketing` |
98
- | `PartnersList` | `CollectionPage` | canonical-list | list | /partners | Free | `marketing` |
99
- | `Partner` | `AboutPage` | canonical-detail | detail | /partners/<slug> | Free | `marketing` |
100
- | `IntegrationsList` | `CollectionPage` | canonical-list | list | /integrations | Free | `marketing` |
101
- | `Integration` | `WebPage` | canonical-detail | detail | /integrations/<slug> | Free | `marketing` |
102
- | `GlossaryList` | `CollectionPage` | canonical-list | list | /glossary | Free | `marketing` |
103
- | `LocationsList` | `CollectionPage` | canonical-list | list | /locations | Free | `marketing` |
104
- | `Location` | `Place` | canonical-detail | detail | /locations/<slug> | Free | `marketing` |
105
- | `DocsList` | `CollectionPage` | canonical-list | list | /docs | Pro | `docs-blog` |
106
- | `SoftwareProduct` | `WebPage` | canonical-detail | detail | /software/<slug> | Free | `marketing` |
107
- | `ServiceAreaPage` | `WebPage` | canonical-detail | detail | /service-areas/<slug> | Free | `marketing` |
108
- | `Pricing` | `WebPage` | canonical-detail | detail | /pricing | Free | `marketing` |
109
-
110
- ## Editorial
111
-
112
- | Key | `@type` | Family | Chassis | Route | Level | Module |
113
- | --- | --- | --- | --- | --- | --- | --- |
114
- | `BlogList` | `Blog` | editorial-list | list | /blog | Free | `marketing` |
115
- | `BlogPosting` | `BlogPosting` | editorial-detail | detail | /blog/<slug> | Pro | `docs-blog` |
116
- | `ResourcesHub` | `CollectionPage` | editorial-list | list | /resources | Free | `marketing` |
117
- | `ResourceDetail` | `Article` | editorial-detail | detail | /resources/<slug> | Free | `marketing` |
118
- | `CaseStudiesList` | `CollectionPage` | editorial-list | list | /case-studies | Free | `marketing` |
119
- | `CaseStudy` | `Article` | editorial-detail | detail | /case-studies/<slug> | Free | `marketing` |
120
- | `HowTo` | `HowTo` | editorial-detail | detail | /how-to/<slug> | Free | `marketing` |
121
- | `Whitepaper` | `Article` | editorial-detail | detail | /whitepapers/<slug> | Free | `marketing` |
122
- | `TechArticle` | `TechArticle` | editorial-detail | detail | /tech-articles/<slug> | Free | `marketing` |
123
- | `DocsDetail` | `TechArticle` | editorial-detail | detail | /docs/<slug> | Pro | `docs-blog` |