create-ailk 0.10.0 → 0.11.1
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 +7 -2
- package/dist/cli.js +5 -1
- package/dist/content-payloads.d.ts +37 -0
- package/dist/content-payloads.js +64 -0
- package/dist/lib/fetch-module.d.ts +3 -3
- package/dist/lib/fetch-module.js +11 -5
- package/dist/lib/fetch-template.d.ts +1 -1
- package/dist/lib/fetch-template.js +7 -4
- package/dist/lib/scaffold-render-sweep.d.ts +16 -0
- package/dist/lib/scaffold-render-sweep.js +52 -3
- package/dist/module-architecture.d.ts +9 -0
- package/dist/module-architecture.js +43 -12
- package/dist/parse-args.d.ts +2 -0
- package/dist/parse-args.js +7 -0
- package/dist/surfaces.js +2 -3
- package/package.json +3 -3
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +4 -3
- package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +8 -8
- package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +8 -8
- package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +8 -8
- package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +8 -8
- package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +8 -8
- package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +8 -8
- package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +8 -8
- package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +8 -8
- package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +8 -8
- package/templates/.claude/settings.json +12 -0
- package/templates/.env.example +1 -5
- package/templates/CLAUDE.md +38 -22
- package/templates/README.md +12 -12
- package/templates/apps/api/jest.config.cjs +1 -0
- package/templates/apps/api/package.json +2 -1
- package/templates/apps/mcp/CLAUDE.md +23 -10
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +160 -17
- package/templates/apps/mcp/__tests__/index.test.ts +118 -1
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +425 -5
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +67 -3
- package/templates/apps/mcp/__tests__/server.test.ts +162 -1
- package/templates/apps/mcp/package.json +1 -1
- package/templates/apps/mcp/src/index.ts +35 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +193 -24
- package/templates/apps/mcp/src/server.ts +77 -5
- package/templates/apps/mcp/src/tools/purchase.ts +1 -1
- package/templates/apps/web/CLAUDE.md +1 -1
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +15 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +10 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +1 -14
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/for/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +1 -14
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +1 -15
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +1 -14
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +4 -16
- package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +3 -15
- package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +1 -14
- package/templates/apps/web/src/lib/commerce-claim.ts +15 -6
- package/templates/content/_site.mdx +23 -4
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/versions.json +34 -34
- package/templates/.claude/skills/README.md +0 -37
- package/templates/.claude/skills/add-auth/SKILL.md +0 -313
- package/templates/.claude/skills/add-auth/references/env-vars.md +0 -106
- package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +0 -180
- package/templates/.claude/skills/add-page/SKILL.md +0 -260
- package/templates/.claude/skills/add-page/references/page-types.md +0 -123
- package/templates/.claude/skills/add-page/scripts/site-pages.mjs +0 -87
- package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +0 -99
- package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +0 -18
- package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +0 -43
- package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +0 -52
- package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +0 -38
- package/templates/.claude/skills/check-site/SKILL.md +0 -245
- package/templates/.claude/skills/check-site/references/common-errors.md +0 -116
- package/templates/.claude/skills/check-site/references/findings-glossary.md +0 -140
- package/templates/.claude/skills/check-site/references/severity-levels.md +0 -114
- package/templates/.claude/skills/fill-page/SKILL.md +0 -199
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +0 -80
- package/templates/.claude/skills/fill-page/references/section-sources.md +0 -41
- package/templates/.claude/skills/setup-database/SKILL.md +0 -156
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +0 -154
- package/templates/.claude/skills/setup-deploy/SKILL.md +0 -312
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +0 -204
- package/templates/.claude/skills/setup-deploy/references/vercel-json-template.md +0 -98
- package/templates/.claude/skills/update-page/SKILL.md +0 -117
- package/templates/.claude/skills/update-page/scripts/update-page.ts +0 -640
- package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +0 -31
- package/templates/.claude/skills/upgrade-plan/SKILL.md +0 -121
- package/templates/.claude/skills/upgrade-plan/references/options.md +0 -24
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +0 -527
- package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +0 -75
- package/templates/apps/mcp/src/lib/require-paid-tier.ts +0 -44
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: setup-database
|
|
3
|
-
description: Take this site from no database to a working lead inbox — provision Postgres, set the connection variables, create the tables, pin the owner, and prove it with one test message that must appear in the inbox. Use when the user asks "set up the database", "I want a lead inbox", "keep my leads", "collect newsletter signups", "connect Postgres", or when setup-deploy points here. Confirms before every write and never prints a connection string.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# setup-database
|
|
7
|
-
|
|
8
|
-
A new site needs no database: contact-form messages are emailed to the
|
|
9
|
-
owner (when the email key is set). A database turns on the **lead inbox**
|
|
10
|
-
— every message kept at `/leads`, reviewed signed in — and newsletter
|
|
11
|
-
signups. This skill takes the site from no database to that inbox, and
|
|
12
|
-
does not call it done until one test message is in it.
|
|
13
|
-
|
|
14
|
-
## When to use
|
|
15
|
-
|
|
16
|
-
- "set up the database" / "connect Postgres"
|
|
17
|
-
- "I want a lead inbox" / "keep every message"
|
|
18
|
-
- "collect newsletter signups"
|
|
19
|
-
- `setup-deploy` pointed here from its lead-delivery step
|
|
20
|
-
|
|
21
|
-
Skip it when the site only needs messages by email — that is
|
|
22
|
-
`setup-deploy`'s email key, no database required.
|
|
23
|
-
|
|
24
|
-
## Rules for every step
|
|
25
|
-
|
|
26
|
-
- **Confirm before every write.** Say what file or command is next and
|
|
27
|
-
wait for a yes.
|
|
28
|
-
- **Never echo a connection string.** A database URL contains a
|
|
29
|
-
password. Refer to it as "your `DATABASE_URL`", never print it, never
|
|
30
|
-
commit it. It goes in `.env.local` (gitignored) and the hosting
|
|
31
|
-
dashboards only.
|
|
32
|
-
- **Stop on the first failure** and say which step failed and why
|
|
33
|
-
(see "When a step fails"). Never skip ahead.
|
|
34
|
-
|
|
35
|
-
## The mechanic
|
|
36
|
-
|
|
37
|
-
### Step 1 — Detect
|
|
38
|
-
|
|
39
|
-
Look for `DATABASE_URL` in `.env.local`, then `.env`. If it is already
|
|
40
|
-
set, say so (without printing it) and go to Step 4.
|
|
41
|
-
|
|
42
|
-
### Step 2 — Provision Postgres
|
|
43
|
-
|
|
44
|
-
Ask which Postgres the owner wants. Any provider that gives a **pooled**
|
|
45
|
-
and a **direct** connection URL works (Neon, Supabase, Railway, RDS, …).
|
|
46
|
-
For local work, the one command in `README.md` § "When you need a
|
|
47
|
-
database":
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
docker run --name ailk-postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:17
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
The owner pastes the two URLs. Write them to `.env.local` only:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
DATABASE_URL=<pooled URL>
|
|
57
|
-
DATABASE_URL_NON_POOLED=<direct URL>
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
(Local Docker: the same URL for both.) Then tell the owner to add the
|
|
61
|
-
same two variables to **both** hosting projects — the API's (it stores
|
|
62
|
-
the leads) and the web app's (sign-in reads the database). Placeholders
|
|
63
|
-
only in any file you show; the real values go in each dashboard.
|
|
64
|
-
|
|
65
|
-
### Step 3 — Create the tables, then the default workspace
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
pnpm db:migrate:deploy
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
pnpm db:seed
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
The seed creates the default Organization and Workspace every lead is
|
|
76
|
-
filed under. Without it the API answers `503 tenant_unresolved` to every
|
|
77
|
-
submission.
|
|
78
|
-
|
|
79
|
-
### Step 4 — Pin the owner, and make sure they can sign in
|
|
80
|
-
|
|
81
|
-
The inbox shows leads only to the site's pinned owner. Ask for the
|
|
82
|
-
email address the owner signs in with and set it — in `.env.local`, and
|
|
83
|
-
on the API project's dashboard:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
AILK_OPERATOR_EMAIL=you@your-domain.com
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
The owner must sign in with that address, and it must be verified by
|
|
90
|
-
the sign-in provider. If no sign-in provider is configured yet, hand off
|
|
91
|
-
to the `add-auth` skill now — the inbox is behind sign-in — and come
|
|
92
|
-
back to Step 5 when it is done.
|
|
93
|
-
|
|
94
|
-
### Step 5 — Send one test message
|
|
95
|
-
|
|
96
|
-
Make a nonce (for example `setup-database 3f9c2a`, a few random
|
|
97
|
-
characters) and submit a lead to the running API (`pnpm dev` locally,
|
|
98
|
-
or the deployed API's URL):
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
curl -sS -X POST <api>/v1/leads/submit \
|
|
102
|
-
-H 'content-type: application/json' \
|
|
103
|
-
-d '{"source":"web","email":"<owner email>","payload":{"name":"setup-database test","message":"<nonce>"}}'
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
It must answer **`201`** with `"persisted": "db"`. Anything else is a
|
|
107
|
-
failed step (see below).
|
|
108
|
-
|
|
109
|
-
### Step 6 — Verify it is in the inbox
|
|
110
|
-
|
|
111
|
-
Build the database package once (its `./tenancy` export resolves to
|
|
112
|
-
`dist/`), then read the inbox with the inbox's own rule — the default
|
|
113
|
-
workspace, newest first, the first page of 50:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
pnpm --filter @working-theory/database build
|
|
117
|
-
pnpm --filter @working-theory/database exec tsx ../.claude/skills/setup-database/scripts/verify-inbox.ts "<nonce>"
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
It prints `found in inbox` and exits 0 only when the test lead is on
|
|
121
|
-
that page. Then ask the owner to open `/leads`, signed in as the pinned
|
|
122
|
-
email, and confirm they see the "setup-database test" row.
|
|
123
|
-
|
|
124
|
-
Do not report the database as set up until verify-inbox.ts exits 0.
|
|
125
|
-
|
|
126
|
-
## When a step fails
|
|
127
|
-
|
|
128
|
-
Say which step failed and the likely cause — never "done":
|
|
129
|
-
|
|
130
|
-
| Symptom | Likely cause |
|
|
131
|
-
| ------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
132
|
-
| `db:migrate:deploy` cannot connect | wrong `DATABASE_URL_NON_POOLED`, or the database is not reachable from here |
|
|
133
|
-
| submit answers `503 tenant_unresolved` (or, with `CONTENT_ADAPTER=database`, the API refuses to start) | Step 3's `pnpm db:seed` did not run |
|
|
134
|
-
| submit answers `201` with `persisted` ≠ `db` | the API process does not see `DATABASE_URL` — restart it, or set it on the API project |
|
|
135
|
-
| verify-inbox exits 1 | the lead landed under another workspace, or 50 newer leads push it off the first page |
|
|
136
|
-
| `/leads` is empty but verify passed | `AILK_OPERATOR_EMAIL` unset, a different or unverified sign-in email, or more than one workspace |
|
|
137
|
-
|
|
138
|
-
## What this skill does not do
|
|
139
|
-
|
|
140
|
-
- Call a database vendor's API. The owner provisions; the skill walks
|
|
141
|
-
them through it.
|
|
142
|
-
- Print, log or commit a connection string.
|
|
143
|
-
- Change application code. The API switches to the inbox by itself once
|
|
144
|
-
`DATABASE_URL` is set.
|
|
145
|
-
|
|
146
|
-
## Coordination
|
|
147
|
-
|
|
148
|
-
- **`setup-deploy`** — its lead-delivery step points here, and its
|
|
149
|
-
`RUNBOOK.md` names this skill as the **Lead inbox** path.
|
|
150
|
-
- **`add-auth`** — the inbox is behind sign-in; Step 4 hands off to it
|
|
151
|
-
when no provider is configured.
|
|
152
|
-
|
|
153
|
-
## Files
|
|
154
|
-
|
|
155
|
-
- `scripts/verify-inbox.ts` — Step 6's read. Exports `findInInbox`
|
|
156
|
-
(pure, takes an injected Prisma-shaped client) and a thin `main()`.
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* verify-inbox — the last step of the `setup-database` skill.
|
|
3
|
-
*
|
|
4
|
-
* Proves the test lead the skill just submitted is in the lead inbox, by
|
|
5
|
-
* reading with the inbox's own predicate: `Lead.siteId` = the default
|
|
6
|
-
* workspace id, newest first, the first page of 50 — exactly what `/leads`
|
|
7
|
-
* shows. A lead that exists somewhere else in the table does not count.
|
|
8
|
-
*
|
|
9
|
-
* Run inside the scaffold's `database` workspace, which owns the Prisma client
|
|
10
|
-
* and its driver adapter. The script's own imports resolve from the WORKING
|
|
11
|
-
* directory (`resolveFromWorkspace`), not from where this file sits — the
|
|
12
|
-
* scaffold root depends on neither package. Build it first — the `./tenancy`
|
|
13
|
-
* export resolves to `dist/`:
|
|
14
|
-
*
|
|
15
|
-
* pnpm --filter @working-theory/database build
|
|
16
|
-
* pnpm --filter @working-theory/database exec tsx ../.claude/skills/setup-database/scripts/verify-inbox.ts <nonce>
|
|
17
|
-
*
|
|
18
|
-
* Exit 0, printing `found in inbox`, only when the nonce's lead is on that
|
|
19
|
-
* page. Exit 1 when it is not; exit 2 on a usage error. Reads only — writes
|
|
20
|
-
* nothing, and prints no lead field but the match.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { createRequire } from "node:module";
|
|
24
|
-
import { join } from "node:path";
|
|
25
|
-
import { pathToFileURL } from "node:url";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Import `specifier` as the package at `cwd` sees it. A bare `import()` here
|
|
29
|
-
* would resolve from this file's own directory (`.claude/skills/…`), where
|
|
30
|
-
* the scaffold installs neither `@working-theory/database` nor its adapter;
|
|
31
|
-
* resolving against `<cwd>/package.json` finds the `database` workspace's own
|
|
32
|
-
* dependencies and its self-reference.
|
|
33
|
-
*/
|
|
34
|
-
export function resolveFromWorkspace(specifier: string, cwd: string = process.cwd()): string {
|
|
35
|
-
return createRequire(join(cwd, "package.json")).resolve(specifier);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function importFromWorkspace<T>(specifier: string): Promise<T> {
|
|
39
|
-
return (await import(pathToFileURL(resolveFromWorkspace(specifier)).href)) as T;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** The inbox's first page (`GET /v1/leads`'s default `limit`). */
|
|
43
|
-
export const INBOX_PAGE_SIZE = 50;
|
|
44
|
-
|
|
45
|
-
interface LeadRow {
|
|
46
|
-
id: string;
|
|
47
|
-
payload: unknown;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** The one Prisma call this script makes — injected, so it is testable without a database. */
|
|
51
|
-
export interface InboxReader {
|
|
52
|
-
lead: {
|
|
53
|
-
findMany(args: {
|
|
54
|
-
where: { siteId: string };
|
|
55
|
-
orderBy: Array<Record<string, "desc">>;
|
|
56
|
-
take: number;
|
|
57
|
-
select: { id: true; payload: true };
|
|
58
|
-
}): Promise<LeadRow[]>;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function carriesNonce(payload: unknown, nonce: string): boolean {
|
|
63
|
-
if (typeof payload !== "object" || payload === null) return false;
|
|
64
|
-
const message = (payload as Record<string, unknown>)["message"];
|
|
65
|
-
return typeof message === "string" && message.includes(nonce);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Whether the lead whose message carries `nonce` is on the inbox's first page. */
|
|
69
|
-
export async function findInInbox(
|
|
70
|
-
db: InboxReader,
|
|
71
|
-
{ workspaceId, nonce }: { workspaceId: string; nonce: string },
|
|
72
|
-
): Promise<boolean> {
|
|
73
|
-
const page = await db.lead.findMany({
|
|
74
|
-
where: { siteId: workspaceId },
|
|
75
|
-
orderBy: [{ createdAt: "desc" }, { id: "desc" }],
|
|
76
|
-
take: INBOX_PAGE_SIZE,
|
|
77
|
-
select: { id: true, payload: true },
|
|
78
|
-
});
|
|
79
|
-
return page.some((row) => carriesNonce(row.payload, nonce));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** `findInInbox` as a process result: 0 + `found in inbox`, or 1 + the reason. */
|
|
83
|
-
export async function verifyInbox(
|
|
84
|
-
db: InboxReader,
|
|
85
|
-
args: { workspaceId: string; nonce: string },
|
|
86
|
-
out: (line: string) => void = (line) => process.stdout.write(`${line}\n`),
|
|
87
|
-
): Promise<0 | 1> {
|
|
88
|
-
if (await findInInbox(db, args)) {
|
|
89
|
-
out("found in inbox");
|
|
90
|
-
return 0;
|
|
91
|
-
}
|
|
92
|
-
out(
|
|
93
|
-
"not in inbox: no lead carrying this nonce is on the default workspace's first page. " +
|
|
94
|
-
"Check that `pnpm db:seed` ran (else the API answers tenant_unresolved) and that " +
|
|
95
|
-
"the test submission returned 201 with persisted \"db\".",
|
|
96
|
-
);
|
|
97
|
-
return 1;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function main(argv: string[]): Promise<number> {
|
|
101
|
-
const nonce = argv[0];
|
|
102
|
-
if (!nonce) {
|
|
103
|
-
process.stderr.write("usage: verify-inbox.ts <nonce>\n");
|
|
104
|
-
return 2;
|
|
105
|
-
}
|
|
106
|
-
// DATABASE_URL lives in the scaffold root's `.env.local` (setup-database
|
|
107
|
-
// writes it there); this runs from `database/`, one level down.
|
|
108
|
-
for (const file of ["../.env.local", "../.env"]) {
|
|
109
|
-
if (process.env.DATABASE_URL) break;
|
|
110
|
-
try {
|
|
111
|
-
process.loadEnvFile(file);
|
|
112
|
-
} catch {
|
|
113
|
-
// absent — try the next one
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (!process.env.DATABASE_URL) {
|
|
117
|
-
process.stderr.write("verify-inbox: DATABASE_URL is not set (.env.local)\n");
|
|
118
|
-
return 1;
|
|
119
|
-
}
|
|
120
|
-
// Resolved inside the scaffold's `database` workspace (see the header).
|
|
121
|
-
// Prisma 7 needs an explicit driver adapter, as `database/seed.ts` does.
|
|
122
|
-
const [{ PrismaClient }, { PrismaPg }, { DEFAULT_WORKSPACE_ID }] = await Promise.all([
|
|
123
|
-
importFromWorkspace<{
|
|
124
|
-
PrismaClient: new (opts: { adapter: unknown }) => InboxReader & {
|
|
125
|
-
$disconnect(): Promise<void>;
|
|
126
|
-
};
|
|
127
|
-
}>("@working-theory/database"),
|
|
128
|
-
importFromWorkspace<{ PrismaPg: new (opts: { connectionString: string }) => unknown }>(
|
|
129
|
-
"@prisma/adapter-pg",
|
|
130
|
-
),
|
|
131
|
-
importFromWorkspace<{ DEFAULT_WORKSPACE_ID: string }>("@working-theory/database/tenancy"),
|
|
132
|
-
]);
|
|
133
|
-
const prisma = new PrismaClient({
|
|
134
|
-
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
|
|
135
|
-
});
|
|
136
|
-
try {
|
|
137
|
-
return await verifyInbox(prisma, {
|
|
138
|
-
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
139
|
-
nonce,
|
|
140
|
-
});
|
|
141
|
-
} finally {
|
|
142
|
-
await prisma.$disconnect();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (/verify-inbox\.ts$/.test(process.argv[1] ?? "")) {
|
|
147
|
-
main(process.argv.slice(2)).then(
|
|
148
|
-
(code) => process.exit(code),
|
|
149
|
-
(err: unknown) => {
|
|
150
|
-
process.stderr.write(`verify-inbox: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
151
|
-
process.exit(1);
|
|
152
|
-
},
|
|
153
|
-
);
|
|
154
|
-
}
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: setup-deploy
|
|
3
|
-
description: Emit deploy configuration for an AILK consumer site — `vercel.json` and `RUNBOOK.md`, plus an env checklist read from the scaffold's own `.env.example`. Vercel-only at v1; other platforms reject with a clear "not yet supported" message. Use when the user asks "set up Vercel", "provision deploy config", "what env vars do I need for production?", "I'm ready to ship to Vercel", or otherwise asks about pre-deploy file setup. Asks where leads should go — the email key (`RESEND_API_KEY` + the two notification addresses) or a database (`setup-database`) — and warns when neither is set. The RUNBOOK documents the two lead destinations: Email (default) and Lead inbox (setup-database).
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# setup-deploy
|
|
7
|
-
|
|
8
|
-
Emit `vercel.json` and `RUNBOOK.md` at the consumer's project root,
|
|
9
|
-
and bring the scaffold's own `.env.example` up to date with what the
|
|
10
|
-
site's code reads, ready for a Vercel deploy. Vercel is the only valid
|
|
11
|
-
`platform` value at v1. Other platforms (Netlify, Cloudflare, Render,
|
|
12
|
-
Fly, self-host) reject with an explicit "not yet supported" message
|
|
13
|
-
and emit nothing.
|
|
14
|
-
|
|
15
|
-
## When to use
|
|
16
|
-
|
|
17
|
-
The consumer is preparing to deploy their AILK site for the first
|
|
18
|
-
time, or wants to verify their deploy config is complete. Triggers:
|
|
19
|
-
|
|
20
|
-
- "provision Vercel config" / "set up Vercel"
|
|
21
|
-
- "what env vars do I need to deploy?"
|
|
22
|
-
- "I'm ready to ship — what files do I need?"
|
|
23
|
-
- "regenerate `vercel.json`" / "refresh `.env.example`"
|
|
24
|
-
|
|
25
|
-
Skip this skill when:
|
|
26
|
-
|
|
27
|
-
- The consumer is on a non-Vercel platform — emit the rejection
|
|
28
|
-
message (see Step 1) and stop.
|
|
29
|
-
- The consumer asks about runtime environment values
|
|
30
|
-
(`process.env.X` debugging) — that's an app-level concern, not a
|
|
31
|
-
config-emission concern.
|
|
32
|
-
- The consumer is asking about Stripe live-mode setup — that's
|
|
33
|
-
hard-floor blocked across AILK; sandbox-only at v1. Mention and stop.
|
|
34
|
-
|
|
35
|
-
## What this skill does (and does not do)
|
|
36
|
-
|
|
37
|
-
**Does:**
|
|
38
|
-
|
|
39
|
-
- Validate the `platform` parameter — only `vercel` is accepted at v1.
|
|
40
|
-
- Detect existing `vercel.json`, `.env.example`, and `RUNBOOK.md` at
|
|
41
|
-
the consumer's project root.
|
|
42
|
-
- For each artifact, ask the consumer how to handle conflicts
|
|
43
|
-
(merge / overwrite / skip) before writing.
|
|
44
|
-
- Emit `vercel.json` and `RUNBOOK.md` at the consumer's project root,
|
|
45
|
-
derived from templates in `references/`.
|
|
46
|
-
- Read the env contract from the scaffold's own `.env.example` — the
|
|
47
|
-
file every scaffold ships at its root — plus a `process.env` scan
|
|
48
|
-
of the site's code, and turn it into the env checklist (Step 5).
|
|
49
|
-
- Ask where leads go (Step 5a) — the email key, or a database via
|
|
50
|
-
`setup-database` — and warn, verbatim, when neither is set.
|
|
51
|
-
- Validate the emitted output (parse `vercel.json` as JSON, check
|
|
52
|
-
`.env.example` declares every variable the `process.env` scan
|
|
53
|
-
found, confirm `RUNBOOK.md` has the two lead-destination headings).
|
|
54
|
-
|
|
55
|
-
**Does not:**
|
|
56
|
-
|
|
57
|
-
- Call any Vercel API. The consumer runs `vercel deploy` themselves.
|
|
58
|
-
- Read or write live secrets. `.env.example` uses placeholder values
|
|
59
|
-
only.
|
|
60
|
-
- Configure Cloudflare anything (deploy target, KV, D1, R2). CF is
|
|
61
|
-
deferred entirely as a coherent follow-up unit.
|
|
62
|
-
- Emit Netlify / Render / Fly / self-host config. v1 is Vercel only.
|
|
63
|
-
- Modify the consumer's actual `.env.local` or any other file
|
|
64
|
-
containing real secrets.
|
|
65
|
-
|
|
66
|
-
## The mechanic
|
|
67
|
-
|
|
68
|
-
### Step 1 — Validate the platform parameter
|
|
69
|
-
|
|
70
|
-
Default platform is `vercel`. If the consumer asks for any other
|
|
71
|
-
platform, respond:
|
|
72
|
-
|
|
73
|
-
> "`setup-deploy` is Vercel-only at v1. Netlify / Cloudflare /
|
|
74
|
-
> Render / Fly / self-host support are tracked at the upstream repo —
|
|
75
|
-
> file an issue at <https://github.com/working-theory-labs/ai-launch-kit/issues>
|
|
76
|
-
> if your platform is missing.
|
|
77
|
-
>
|
|
78
|
-
> Cloudflare is deferred entirely (deploy target + KV/D1/R2 data
|
|
79
|
-
> layer) as a coherent follow-up."
|
|
80
|
-
|
|
81
|
-
Then stop. Do not emit any files.
|
|
82
|
-
|
|
83
|
-
### Step 2 — Detect existing artifacts
|
|
84
|
-
|
|
85
|
-
Look for `vercel.json`, `.env.example`, and `RUNBOOK.md` at the
|
|
86
|
-
consumer's project root. For each one, note:
|
|
87
|
-
|
|
88
|
-
- **Missing** — will emit fresh.
|
|
89
|
-
- **Present** — ask how to handle (Step 3).
|
|
90
|
-
|
|
91
|
-
`.env.example` is the exception: every scaffold ships one, and it is
|
|
92
|
-
the source this skill reads, not a file it writes from scratch. If
|
|
93
|
-
it is missing, tell the consumer to restore it from git history and
|
|
94
|
-
re-run — never write one from memory.
|
|
95
|
-
|
|
96
|
-
### Step 3 — Conflict handling per file
|
|
97
|
-
|
|
98
|
-
For each existing artifact, show the consumer:
|
|
99
|
-
|
|
100
|
-
1. The first ~10 lines of the existing file (preview).
|
|
101
|
-
2. A summary of what the skill would emit (template name + key
|
|
102
|
-
fields; for `.env.example`, the variables Step 5 would add).
|
|
103
|
-
3. Three options: **merge** (only for `RUNBOOK.md` and
|
|
104
|
-
`.env.example` — see merge logic below), **overwrite**
|
|
105
|
-
(replace contents; not offered for `.env.example`, which has no
|
|
106
|
-
other source), **skip** (leave existing file untouched).
|
|
107
|
-
|
|
108
|
-
Wait for the consumer's choice per file. Do not blindly overwrite.
|
|
109
|
-
|
|
110
|
-
**Merge logic — `.env.example`:** if a var the Step 5 `process.env`
|
|
111
|
-
scan found is **missing** from every `.env.example` Step 5 reads —
|
|
112
|
-
neither set (`NAME=`) nor listed commented (`# NAME=`) — append it
|
|
113
|
-
(with comment) at the bottom of the root file
|
|
114
|
-
under a `# ── Added by setup-deploy ──` heading. Lines already in
|
|
115
|
-
the file are never modified; the consumer's comments and values win.
|
|
116
|
-
|
|
117
|
-
**Merge logic — `RUNBOOK.md`:** if the "Leads — where messages go"
|
|
118
|
-
section (with its two headings, `Email (default)` and
|
|
119
|
-
`Lead inbox (setup-database)`) is missing from the existing file,
|
|
120
|
-
append it at the end. Existing sections are not modified.
|
|
121
|
-
|
|
122
|
-
**Merge logic — `vercel.json`:** **not supported.** `vercel.json` is
|
|
123
|
-
either overwritten or skipped — no field-level merge. Reason: the
|
|
124
|
-
file is small and consumers are likely to have customized it
|
|
125
|
-
intentionally; merging risks subtle config drift.
|
|
126
|
-
|
|
127
|
-
### Step 4 — Emit `vercel.json`
|
|
128
|
-
|
|
129
|
-
Use the template at `references/vercel-json-template.md`. The
|
|
130
|
-
emitted file has these fields:
|
|
131
|
-
|
|
132
|
-
- `version: 2` — Vercel config schema version.
|
|
133
|
-
- `framework: "nextjs"` — Vercel auto-detects `apps/web` as Next.js.
|
|
134
|
-
- `buildCommand` — pnpm-aware build (typically
|
|
135
|
-
`cd ../.. && pnpm install --frozen-lockfile && pnpm --filter ./apps/web build`
|
|
136
|
-
for monorepo deploys, or `pnpm build` for single-app deploys).
|
|
137
|
-
- `installCommand` — match the consumer's package manager.
|
|
138
|
-
- `env` — env-var passthrough; AILK ships an empty `env: {}` and
|
|
139
|
-
expects Vercel's project-level env-var dashboard to handle real
|
|
140
|
-
values.
|
|
141
|
-
|
|
142
|
-
After write, parse the JSON. If parse fails, the skill has a bug —
|
|
143
|
-
report it to the consumer with the literal error.
|
|
144
|
-
|
|
145
|
-
### Step 5 — Build the env checklist from `.env.example`
|
|
146
|
-
|
|
147
|
-
The env contract is the scaffold's own `.env.example` at the project
|
|
148
|
-
root, plus the per-app `apps/*/.env.example` files it points to. Read
|
|
149
|
-
them; keep no second copy of them anywhere, including in this skill.
|
|
150
|
-
|
|
151
|
-
1. **Read the declared names.** In each of those files, every
|
|
152
|
-
uncommented `NAME=value` line is a variable the scaffold sets (for
|
|
153
|
-
example `CONTENT_ADAPTER=mdx`); every commented `# NAME=` line is
|
|
154
|
-
an opt-in variable. The `# ── <Section> ──` heading above a line
|
|
155
|
-
is its group, and the comment above an entry says what it does
|
|
156
|
-
and whether production needs it. A name counts as declared when
|
|
157
|
-
any of the files declares it.
|
|
158
|
-
2. **Scan the code.** Grep the site's source (`apps/`, `database/`,
|
|
159
|
-
and `packages/` if the scaffold has one) for `process.env.NAME`
|
|
160
|
-
and `process.env["NAME"]`. Skip `NODE_ENV`, variables the platform
|
|
161
|
-
sets itself (`VERCEL_*`, `CI`), and any name the files' comments
|
|
162
|
-
call deprecated — the code reads those only as a fallback, and the
|
|
163
|
-
checklist uses the name that replaced them.
|
|
164
|
-
3. **Add what is missing.** A name the scan found that `.env.example`
|
|
165
|
-
does not declare goes at the bottom under
|
|
166
|
-
`# ── Added by setup-deploy ──`, commented out with an empty
|
|
167
|
-
placeholder and a one-line comment naming the file that reads it.
|
|
168
|
-
Do this only when the consumer chose **merge** in Step 3; on
|
|
169
|
-
**skip**, list those names in the checklist instead. Either way,
|
|
170
|
-
never silently omit a var the consumer's site reads.
|
|
171
|
-
4. **Show the checklist.** List each section heading with its
|
|
172
|
-
variables, marking the ones the file sets, and quote the entry's
|
|
173
|
-
own comment for what production needs. Call out every value the
|
|
174
|
-
file sets that the consumer must change for their own site — for
|
|
175
|
-
example `AILK_PUBLIC_DOMAIN`, and `STEALTH`, which stays `true`
|
|
176
|
-
until launch. Use the names exactly as `.env.example` spells
|
|
177
|
-
them — do not restate a variable from memory.
|
|
178
|
-
|
|
179
|
-
The skill **does not** read live `.env` files. Placeholder values
|
|
180
|
-
only. No `sk_live_*`, no `pk_live_*`, no real Resend keys, no real
|
|
181
|
-
auth secrets.
|
|
182
|
-
|
|
183
|
-
### Step 5a — Lead delivery
|
|
184
|
-
|
|
185
|
-
Where a contact-form message goes is decided by the API project's
|
|
186
|
-
environment (RUNBOOK § "Leads — where messages go"). Ask the consumer
|
|
187
|
-
two questions:
|
|
188
|
-
|
|
189
|
-
1. **Will this site use a database?** If yes, point them to the
|
|
190
|
-
`setup-database` skill — it provisions Postgres, sets
|
|
191
|
-
`DATABASE_URL`, runs migrations and verifies a test lead reaches
|
|
192
|
-
the inbox. Do not collect a connection string here.
|
|
193
|
-
2. **What email should messages go to?** Ask for
|
|
194
|
-
`RESEND_API_KEY`, `AILK_LEAD_NOTIFICATION_TO` (their address) and
|
|
195
|
-
`AILK_LEAD_NOTIFICATION_FROM` (a sender on their Resend-verified
|
|
196
|
-
domain). All three are needed — a key alone sends nothing.
|
|
197
|
-
`.env.example` already lists all three as commented entries; tell
|
|
198
|
-
the consumer to set the real values in the **API** project's
|
|
199
|
-
dashboard. Never write a key into a file.
|
|
200
|
-
|
|
201
|
-
If the consumer provides neither a database nor all three email
|
|
202
|
-
values, print this, verbatim:
|
|
203
|
-
|
|
204
|
-
> ⚠ No lead destination is set. In production your contact page will show
|
|
205
|
-
> your contact email instead of the form, and no messages will be collected.
|
|
206
|
-
> Run setup-database, or set RESEND_API_KEY, AILK_LEAD_NOTIFICATION_TO and
|
|
207
|
-
> AILK_LEAD_NOTIFICATION_FROM on the API project.
|
|
208
|
-
|
|
209
|
-
Then continue — the warning does not stop the deploy config.
|
|
210
|
-
|
|
211
|
-
### Step 6 — Emit `RUNBOOK.md`
|
|
212
|
-
|
|
213
|
-
Use the template at `references/runbook-template.md`. The emitted
|
|
214
|
-
runbook has these sections:
|
|
215
|
-
|
|
216
|
-
1. **Pre-deploy checklist** — what to set in Vercel's project
|
|
217
|
-
dashboard before clicking Deploy.
|
|
218
|
-
2. **Leads — where messages go** — the two lead destinations, in
|
|
219
|
-
plain words:
|
|
220
|
-
- **Email (default)** — no database; each message is emailed to
|
|
221
|
-
the owner (`RESEND_API_KEY`, `AILK_LEAD_NOTIFICATION_TO`,
|
|
222
|
-
`AILK_LEAD_NOTIFICATION_FROM` on the API project).
|
|
223
|
-
- **Lead inbox (setup-database)** — `DATABASE_URL` set; messages
|
|
224
|
-
land in `/leads` and newsletter signups are collected.
|
|
225
|
-
With neither, production `/contact` shows the site's contact email
|
|
226
|
-
instead of the form. On a non-Vercel host, the API process needs
|
|
227
|
-
`NODE_ENV=production`.
|
|
228
|
-
3. **Custom domain + DNS** — pointer at Vercel's domain docs.
|
|
229
|
-
4. **Production callbacks for OAuth** — for consumers who used
|
|
230
|
-
`add-auth` and need to register prod redirect URIs.
|
|
231
|
-
|
|
232
|
-
After write, grep the RUNBOOK for the two lead-destination headings
|
|
233
|
-
(`Email (default)`, `Lead inbox (setup-database)`). Both must be
|
|
234
|
-
present. If either is missing, the skill has a bug — flag with the
|
|
235
|
-
literal error.
|
|
236
|
-
|
|
237
|
-
### Step 7 — Closing summary
|
|
238
|
-
|
|
239
|
-
Print:
|
|
240
|
-
|
|
241
|
-
- Which artifacts were emitted vs skipped (per consumer's Step 3
|
|
242
|
-
choices).
|
|
243
|
-
- Where leads will go (email, the lead inbox, or — with the Step 5a
|
|
244
|
-
warning repeated — nowhere), with a pointer to `RUNBOOK.md`'s
|
|
245
|
-
"Leads — where messages go" section.
|
|
246
|
-
- A reminder that Vercel project-level env-vars need to be set in
|
|
247
|
-
the Vercel dashboard before deploy — `.env.example` is the
|
|
248
|
-
contract, not the values.
|
|
249
|
-
- The next step: `vercel deploy` (or `git push` if the consumer is
|
|
250
|
-
using Vercel's GitHub integration).
|
|
251
|
-
|
|
252
|
-
## Re-invocation
|
|
253
|
-
|
|
254
|
-
The skill is idempotent. Re-running on a project with all three
|
|
255
|
-
artifacts present asks the consumer how to handle each conflict
|
|
256
|
-
(per Step 3). A consumer who picks **skip** for all three gets a
|
|
257
|
-
no-op run with a "all artifacts already present, nothing to do"
|
|
258
|
-
message.
|
|
259
|
-
|
|
260
|
-
## Validation
|
|
261
|
-
|
|
262
|
-
Before reporting "done":
|
|
263
|
-
|
|
264
|
-
- `vercel.json` parses as JSON ✓
|
|
265
|
-
- Every variable the `process.env` scan found is declared in an
|
|
266
|
-
`.env.example`, set or commented — or, when the consumer chose
|
|
267
|
-
**skip**, listed in the checklist ✓
|
|
268
|
-
- `RUNBOOK.md` contains both lead-destination headings
|
|
269
|
-
(`Email (default)`, `Lead inbox (setup-database)`) ✓
|
|
270
|
-
- Step 5a ran: the consumer chose a lead destination, or saw the
|
|
271
|
-
no-destination warning ✓
|
|
272
|
-
|
|
273
|
-
If any check fails, surface the failure to the consumer (not silent).
|
|
274
|
-
|
|
275
|
-
## What this skill does NOT validate
|
|
276
|
-
|
|
277
|
-
- That the consumer's Vercel project actually deploys. That's
|
|
278
|
-
`vercel deploy`'s job.
|
|
279
|
-
- That env-var values in the consumer's Vercel dashboard are
|
|
280
|
-
correct or live. The skill never reads dashboard state.
|
|
281
|
-
- That OAuth callback URLs in `add-auth`'s RUNBOOK are
|
|
282
|
-
registered in production OAuth apps. That's the consumer's job
|
|
283
|
-
pre-launch.
|
|
284
|
-
|
|
285
|
-
## References
|
|
286
|
-
|
|
287
|
-
- `references/vercel-json-template.md` — minimal viable `vercel.json`.
|
|
288
|
-
- The scaffold's own `.env.example` at the project root, and the
|
|
289
|
-
per-app `apps/*/.env.example` files — the env contract Step 5
|
|
290
|
-
reads. This skill keeps no copy of it.
|
|
291
|
-
- `references/runbook-template.md` — `RUNBOOK.md` with the two lead
|
|
292
|
-
destinations (Email (default), Lead inbox (setup-database)).
|
|
293
|
-
|
|
294
|
-
## Coordination
|
|
295
|
-
|
|
296
|
-
- **`setup-database`** — takes a site from no database to a working
|
|
297
|
-
lead inbox. Step 5a hands off to it; this skill never collects a
|
|
298
|
-
connection string.
|
|
299
|
-
- **`add-auth`** — produces its own `RUNBOOK.md`
|
|
300
|
-
sections for OAuth setup (dev callback URLs). This skill's
|
|
301
|
-
RUNBOOK adds production callback URLs and merges with any
|
|
302
|
-
existing runbook content (not blindly overwrites).
|
|
303
|
-
- **`@working-theory/env`** — at v1 a placeholder package, not yet a Zod
|
|
304
|
-
schema. This skill reads the env contract from the scaffold's
|
|
305
|
-
`.env.example`, not from a runtime schema. When `@working-theory/env`
|
|
306
|
-
ships its schema, this skill should be revised to read from it.
|
|
307
|
-
- **Stripe sandbox-only** — hard-floor enforced globally. Nothing
|
|
308
|
-
this skill adds to `.env.example` carries a `sk_live_*` or
|
|
309
|
-
`pk_live_*` value.
|
|
310
|
-
- **Cloudflare deferred** — neither CF Pages (deploy target) nor
|
|
311
|
-
CF KV / D1 / R2 (data layer) is in v1. Will ship as a coherent
|
|
312
|
-
follow-up PR.
|