create-ailk 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -14
- package/component-catalog.json +735 -95
- package/component-catalog.md +13 -7
- package/dist/add-module-core.d.ts +22 -0
- package/dist/add-module-core.js +50 -0
- package/dist/add-module.d.ts +89 -0
- package/dist/add-module.js +334 -0
- package/dist/category-profile.d.ts +126 -0
- package/dist/category-profile.js +262 -0
- package/dist/cli.js +89 -29
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/lib/extract-module-bundle.d.ts +56 -1
- package/dist/lib/extract-module-bundle.js +170 -14
- package/dist/lib/license-key-files.d.ts +15 -0
- package/dist/lib/license-key-files.js +102 -0
- package/dist/lib/module-license-gate.d.ts +79 -0
- package/dist/lib/module-license-gate.js +160 -12
- package/dist/lib/module-tier.d.ts +45 -16
- package/dist/lib/module-tier.js +76 -0
- package/dist/lib/pro-callout.d.ts +16 -10
- package/dist/lib/pro-callout.js +34 -3
- package/dist/license-set.d.ts +34 -0
- package/dist/license-set.js +194 -0
- package/dist/module-architecture.d.ts +20 -0
- package/dist/module-architecture.js +77 -19
- package/dist/modules/commerce/0.6.1.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
- package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
- package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
- package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
- package/dist/modules/workspace/0.6.1.tar.gz +0 -0
- package/dist/parse-args.d.ts +85 -10
- package/dist/parse-args.js +227 -15
- package/dist/programmatic.d.ts +81 -5
- package/dist/programmatic.js +291 -32
- package/dist/site-chrome.d.ts +18 -7
- package/dist/site-chrome.js +32 -14
- package/dist/site-roster.d.ts +127 -0
- package/dist/site-roster.js +499 -0
- package/dist/starter-grid.d.ts +216 -0
- package/dist/starter-grid.js +449 -0
- package/dist/surfaces.d.ts +20 -7
- package/dist/surfaces.js +204 -24
- package/dist/sync-routes.d.ts +9 -0
- package/dist/sync-routes.js +2 -2
- package/dist/template-manifest.d.ts +94 -19
- package/dist/template-manifest.js +170 -289
- package/dist/template-names.d.ts +14 -12
- package/dist/template-names.js +13 -17
- package/package.json +6 -4
- package/site-templates/_base/content/en/BookCall/book-call.mdx +52 -0
- package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
- package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +65 -0
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +67 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
- package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +17 -3
- package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
- package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
- package/site-templates/designs/minimalism/DESIGN.md +162 -0
- package/site-templates/designs/standard/DESIGN.md +167 -0
- package/site-templates/marketing-site/DESIGN.md +59 -40
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +376 -57
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +230 -90
- package/site-templates/marketing-site/ailk-template.json +1 -0
- package/site-templates/marketing-site-free/DESIGN.md +59 -40
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +323 -63
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +323 -113
- package/site-templates/marketing-site-free/ailk-template.json +3 -2
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
- package/site-templates/minimalism-site/DESIGN.md +54 -40
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +173 -51
- package/site-templates/minimalism-site/ailk-template.json +3 -2
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/saas-product/DESIGN.md +59 -40
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -0
- package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
- package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
- package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/shapes/local-business-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
- package/site-templates/shapes/local-business-service/provenance.json +4 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +362 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -0
- package/site-templates/shapes/organization-service/ailk-template.json +10 -0
- package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/shapes/organization-service/provenance.json +4 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -0
- package/site-templates/shapes/person-service/ailk-template.json +10 -0
- package/site-templates/shapes/person-service/content/_site.mdx +85 -0
- package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
- package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
- package/site-templates/shapes/person-service/provenance.json +4 -0
- package/templates/.claude/settings.json +15 -0
- package/templates/.claude/skills/README.md +10 -9
- package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
- package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
- package/templates/.claude/skills/add-page/references/page-types.md +115 -0
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
- package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
- package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
- package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
- package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
- package/templates/.claude/skills/fill-page/SKILL.md +180 -0
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
- package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
- package/templates/.claude/skills/setup-database/SKILL.md +156 -0
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +73 -41
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +21 -30
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +204 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
- package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
- package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/CLAUDE.md +8 -2
- package/templates/DESIGN.md +56 -44
- package/templates/README.md +116 -46
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/CLAUDE.md +3 -2
- package/templates/apps/api/package.json +1 -1
- package/templates/apps/api/src/config/__tests__/index.test.ts +146 -10
- package/templates/apps/api/src/config/index.ts +46 -14
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
- package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
- package/templates/apps/api/src/lib/license-key-store.ts +45 -0
- package/templates/apps/api/src/lib/site.ts +5 -3
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
- package/templates/apps/api/src/middleware/auth.ts +9 -0
- package/templates/apps/api/src/openapi/spec.ts +63 -12
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
- package/templates/apps/api/src/routes/content/get-page.ts +86 -2
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
- package/templates/apps/api/src/routes/leads/index.ts +46 -1
- package/templates/apps/api/src/server.ts +13 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
- package/templates/apps/api/src/services/lead-routing.ts +140 -30
- package/templates/apps/api/src/services/leads-inbox.ts +48 -11
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
- package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
- package/templates/apps/mcp/package.json +2 -1
- package/templates/apps/mcp/src/index.ts +7 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
- package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
- package/templates/apps/mcp/src/tools/create_project.ts +21 -6
- package/templates/apps/web/__tests__/setup.ts +18 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
- package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +67 -29
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +94 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +228 -86
- package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
- package/templates/apps/web/app/[locale]/contact/page.tsx +115 -7
- package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/layout.tsx +146 -52
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +142 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/{cancellation → purchase-policy}/page.tsx +15 -10
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +63 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
- package/templates/apps/web/app/error-fallback.tsx +17 -45
- package/templates/apps/web/app/not-found.tsx +14 -38
- package/templates/apps/web/app/og/route.tsx +6 -0
- package/templates/apps/web/eslint.config.mjs +3 -0
- package/templates/apps/web/instrumentation.ts +14 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
- package/templates/apps/web/lib/license-key-store.ts +52 -0
- package/templates/apps/web/lib/nav-config.ts +82 -18
- package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
- package/templates/apps/web/lib/site-brand.tsx +1 -1
- package/templates/apps/web/lib/site-chrome.ts +3 -3
- package/templates/apps/web/lib/site-identity.ts +148 -0
- package/templates/apps/web/lib/site-theme.ts +11 -0
- package/templates/apps/web/messages/en.json +78 -37
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +3 -3
- package/templates/apps/web/public/logomark.svg +6 -3
- package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
- package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +4 -4
- package/templates/apps/web/public/wordmark-light.svg +4 -0
- package/templates/apps/web/public/wordmark.svg +3 -3
- package/templates/apps/web/security-headers.config.ts +5 -1
- package/templates/content/_site.mdx +25 -0
- package/templates/content/en/AboutPage/about.mdx +150 -144
- package/templates/content/en/BlogList/blog.mdx +126 -0
- package/templates/content/en/ContactPage/contact.mdx +21 -92
- package/templates/content/en/FAQPage/faq.mdx +86 -57
- package/templates/content/en/HomePage/home.mdx +268 -217
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +181 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
- package/templates/database/CHANGELOG.md +14 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +3 -1
- package/templates/tools/lint-architecture.ts +315 -0
- package/templates/tools/tsconfig.json +14 -0
- package/versions.json +33 -33
- package/site-templates/simple-service/DESIGN.md +0 -148
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +0 -240
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -278
- package/site-templates/simple-service/ailk-template.json +0 -10
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -67
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
- package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
- package/site-templates/simple-service/provenance.json +0 -4
- package/templates/.claude/skills/add-schema/SKILL.md +0 -277
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
- package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
- package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
- package/templates/.claude/skills/provision-config/references/runbook-template.md +0 -248
- package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -150
- package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
- package/templates/apps/web/app/[locale]/shipping/page.tsx +0 -44
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
- package/templates/content/en/LandingPage/blog.mdx +0 -83
- package/templates/content/en/LandingPage/features.mdx +0 -103
- package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fill-page
|
|
3
|
+
description: Fill one existing page's section content from a category profile. Tier 1 writes realistic, labelled sample content for the category (a plumber's home page, say). Tier 2 asks the owner the profile's questions and writes the company's own first draft from their answers and `_site.mdx`. Use when the user says "fill this page", "fill the home page for a plumber", "write sample content for my services page", or "write my real copy from what I tell you". It never creates a page, never changes a page's section stack, and never edits `_site.mdx`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fill-page
|
|
7
|
+
|
|
8
|
+
Replace one page's `{brace}` instructions with content, reading the
|
|
9
|
+
content from a **category profile** (`profiles/<slug>.yaml` beside this
|
|
10
|
+
file). A profile is the data for one business type: the schema.org
|
|
11
|
+
subtype, the typical offerings, the proof buyers expect, the buyer's own
|
|
12
|
+
words, and the questions to ask the owner.
|
|
13
|
+
|
|
14
|
+
Two tiers:
|
|
15
|
+
|
|
16
|
+
| Tier | Source | Result | `placeholder` |
|
|
17
|
+
| ---- | -------------------------------------------------------- | ------------------------------------------ | ------------- |
|
|
18
|
+
| 1 | the profile | realistic sample content for the category | `true` |
|
|
19
|
+
| 2 | `_site.mdx` + the owner's answers (`content/_intake.yaml`) | the company's own first draft | by the rule in Step 2.4 |
|
|
20
|
+
|
|
21
|
+
`placeholder: true` in a page's frontmatter makes the site render a
|
|
22
|
+
**PLACEHOLDER — REPLACE BEFORE LAUNCH** label at the top of the page.
|
|
23
|
+
The label is for people. Structured data reaches agents without it, so
|
|
24
|
+
tier-1 content must be safe to publish by itself (see the never-invent
|
|
25
|
+
list).
|
|
26
|
+
|
|
27
|
+
## Inputs
|
|
28
|
+
|
|
29
|
+
- **The page file** — `content/<locale>/<Type>/<slug>.mdx`. It must
|
|
30
|
+
already exist; `add-page` creates pages.
|
|
31
|
+
- **The page type** — the frontmatter `type`.
|
|
32
|
+
- **The profile slug** — a file under `profiles/` (e.g. `plumbing`).
|
|
33
|
+
Ask if the user did not name one. If there is no profile for their
|
|
34
|
+
category, say so and stop.
|
|
35
|
+
- **The tier** — 1 unless the user asks for their own copy.
|
|
36
|
+
|
|
37
|
+
## What never changes
|
|
38
|
+
|
|
39
|
+
- The page's **section stack**: every section's `id` and `type`, in
|
|
40
|
+
order. Never add, remove or reorder a section. The starter's manifest
|
|
41
|
+
records each page's sections, and a changed stack fails its check.
|
|
42
|
+
- Each section's **layout options** that are already present:
|
|
43
|
+
`variant`, `columns`, `layout`, `initialOpen`, `size` and the like.
|
|
44
|
+
They are the site's locked look.
|
|
45
|
+
- Link targets (`href`) that are already present, except a link to a
|
|
46
|
+
Service page, which follows that page's slug (Step 1.3).
|
|
47
|
+
- `{/* … */}` comments in the MDX body.
|
|
48
|
+
- `_site.mdx`. Identity strings (the company name) are read from it,
|
|
49
|
+
never written to it.
|
|
50
|
+
|
|
51
|
+
## Never invent (tier 1)
|
|
52
|
+
|
|
53
|
+
At tier 1, write none of these. Remove any you find in the page:
|
|
54
|
+
|
|
55
|
+
- `price`, `priceCurrency`, `availability` or `stripe` — each feeds the
|
|
56
|
+
schema.org `Offer` an agent quotes as the company's price.
|
|
57
|
+
- A person's name or another company's name.
|
|
58
|
+
- A phone number, a street address, or a licence or registration number.
|
|
59
|
+
- A town, a county, or a distance: the service area is a company fact.
|
|
60
|
+
- Testimonials: `author` is the literal `Sample customer`; no `company`,
|
|
61
|
+
`rating` or `avatar`.
|
|
62
|
+
- Statistics a reader would take as measured (a count of jobs, a years
|
|
63
|
+
figure). Use the profile's `proof[]` labels as words, not numbers.
|
|
64
|
+
|
|
65
|
+
The company name is the one identity string tier 1 may write, and it
|
|
66
|
+
comes from `_site.mdx` `siteIdentity.name`.
|
|
67
|
+
|
|
68
|
+
## Step 0 — Read
|
|
69
|
+
|
|
70
|
+
1. Read the profile: `profiles/<slug>.yaml`.
|
|
71
|
+
2. Read `content/_site.mdx` for `siteIdentity.name`.
|
|
72
|
+
3. Read the page file. Note its stack (`sections[].type` in order).
|
|
73
|
+
4. For each section type in the stack, read its data schema: the
|
|
74
|
+
`<Type>SectionDataSchema` (or `<Type>DataSchema`) export in the
|
|
75
|
+
installed `@working-theory/validation`'s `schemas/page-sections`. Fill
|
|
76
|
+
only fields the schema declares. Every field a section renders from
|
|
77
|
+
copy gets content; a field the source page did not use stays unused.
|
|
78
|
+
5. Find the route that renders this page type under `apps/web/app/[locale]/`
|
|
79
|
+
and read its `bodyMode`. When it is `"mdx-body"`, the MDX body is
|
|
80
|
+
rendered and must be filled too. Otherwise the body is a short note,
|
|
81
|
+
and you rewrite it as one plain sentence that names no braces.
|
|
82
|
+
|
|
83
|
+
`references/section-sources.md` maps each section type to the profile
|
|
84
|
+
fields and intake answers that fill it.
|
|
85
|
+
|
|
86
|
+
## Step 1 — Tier 1: sample content
|
|
87
|
+
|
|
88
|
+
1. **Sections.** For each section, in place, write every copy field from
|
|
89
|
+
the profile using `references/section-sources.md`:
|
|
90
|
+
- offerings → `items` entries (`title` = the offering `name`,
|
|
91
|
+
`description` = its `job`, `link.href` = `/services/<kebab name>`);
|
|
92
|
+
- vocabulary → headlines, eyebrows and FAQ questions (the buyer's own
|
|
93
|
+
words: the `problem` list for what went wrong, `outcome` for what
|
|
94
|
+
they get);
|
|
95
|
+
- proof → `stats` labels and `testimonials` quotes, under the
|
|
96
|
+
never-invent list.
|
|
97
|
+
Keep `id`, `type` and every layout option.
|
|
98
|
+
2. **Frontmatter.** Rewrite `title` and `description` without braces.
|
|
99
|
+
Use the company name from `_site.mdx` where the page names the
|
|
100
|
+
company. On a page that carries `provider.name`, set it to the company
|
|
101
|
+
name. Drop `schema.areaServed`; set `schema.serviceType` to the
|
|
102
|
+
offering name. Remove every key the never-invent list names.
|
|
103
|
+
3. **Service pages.** A `Service` page is one offering. The user names
|
|
104
|
+
the offering, or you take the first profile offering that no other
|
|
105
|
+
Service page carries yet. Set `title` and `schema.serviceType` to the
|
|
106
|
+
offering `name`, and `slug` and the file name to its name in kebab
|
|
107
|
+
case (`Drain clearing` → `drain-clearing`): rename the file, never
|
|
108
|
+
copy it. To give every offering a page, the user first adds one
|
|
109
|
+
Service page per offering (`add-page`, or a copy of an existing one),
|
|
110
|
+
then runs this skill on each.
|
|
111
|
+
4. **Body.** When the route renders `mdx-body`, rewrite each paragraph
|
|
112
|
+
and list item under the page's existing headings in the category's
|
|
113
|
+
words. Keep the headings, keep the order, keep the comments. Drop
|
|
114
|
+
every `\{…\}` instruction. Under a price heading, say how the job is
|
|
115
|
+
priced (the offering's `pricing`: `quote`, `fixed`, `hourly`,
|
|
116
|
+
`subscription`) in words, with no figure.
|
|
117
|
+
5. **Label.** Set `placeholder: true` in the frontmatter.
|
|
118
|
+
6. **Check** (below).
|
|
119
|
+
|
|
120
|
+
## Step 2 — Tier 2: the company's own draft
|
|
121
|
+
|
|
122
|
+
1. **Ask.** Ask each `intake[]` question from the profile, in order, one
|
|
123
|
+
at a time. Record the answers in `content/_intake.yaml`:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
answers:
|
|
127
|
+
offerings: [Drain clearing, Emergency call-out]
|
|
128
|
+
service_area: "…"
|
|
129
|
+
faq: null # unanswered
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A list answer stays a list. An unanswered or skipped question is
|
|
133
|
+
`null`. Offerings are named exactly as the profile names them.
|
|
134
|
+
2. **Fill.** Fill the page as in Step 1, but from `_site.mdx` and the
|
|
135
|
+
answers first; the profile fills only what the answers leave open.
|
|
136
|
+
- A profile offering the owner does not offer appears on no page:
|
|
137
|
+
remove its `items` entries, and ask before deleting its Service
|
|
138
|
+
page (the one file this skill may delete).
|
|
139
|
+
- A price appears only when the owner gave one, with `priceCurrency`.
|
|
140
|
+
- The company name appears in each page's frontmatter (`title` or
|
|
141
|
+
`description`), and the service area in the home hero when given.
|
|
142
|
+
- A section whose sources have no answer keeps its tier-1 sample
|
|
143
|
+
content.
|
|
144
|
+
3. **Never invent** still holds for everything the owner did not give.
|
|
145
|
+
Testimonials stay `Sample customer` unless the owner supplies a
|
|
146
|
+
customer's name with permission to publish it.
|
|
147
|
+
4. **Label.** Set `placeholder: false` only when every section is filled
|
|
148
|
+
from company facts:
|
|
149
|
+
- every section type in the stack appears in the `fills` of at least
|
|
150
|
+
one intake item whose answer is not empty, and
|
|
151
|
+
- when the stack has a `hero` or `cta`, `_site.mdx` has
|
|
152
|
+
`siteIdentity.name`.
|
|
153
|
+
|
|
154
|
+
A page with no sections is judged on its body: a `Service` page is
|
|
155
|
+
cleared only when the `offerings` answer names its offering; any
|
|
156
|
+
other page with no sections keeps `placeholder: true`. In every other
|
|
157
|
+
case, keep `placeholder: true`.
|
|
158
|
+
5. **Check** (below).
|
|
159
|
+
|
|
160
|
+
## The check
|
|
161
|
+
|
|
162
|
+
Run the site's own structured-data check. It reads every content page
|
|
163
|
+
through the same frontmatter and section schemas the site renders with,
|
|
164
|
+
and validates each page's JSON-LD:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pnpm --filter web test -- lib/__tests__/structured-data.test.tsx
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Then search the filled file for a `{` or `}` in any frontmatter string or
|
|
171
|
+
in the body outside `{/* … */}` comments. Fix everything either reports
|
|
172
|
+
and run both again. Stop only when both are clean.
|
|
173
|
+
|
|
174
|
+
## What this skill does not do
|
|
175
|
+
|
|
176
|
+
- Tier 3 (the Messaging Canvas through the content pipeline).
|
|
177
|
+
- Create a page — use `add-page`.
|
|
178
|
+
- Edit `_site.mdx`, the nav or the page roster. A profile's `roster` and
|
|
179
|
+
`nav` entries are declared for a later step and are not applied here.
|
|
180
|
+
- Write a price, a name or an address the owner did not give.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Category profile — Local Plumbing (#6640; starter-template contract § D).
|
|
2
|
+
# Read by the `fill-page` skill: tier 1 fills a page with labelled sample
|
|
3
|
+
# content from this file; tier 2 asks `intake[]` and fills from the answers.
|
|
4
|
+
# Twin: templates/.claude/skills/fill-page/profiles/plumbing.yaml (byte-identical).
|
|
5
|
+
slug: plumbing
|
|
6
|
+
shape: local-business-service
|
|
7
|
+
subtype: Plumber
|
|
8
|
+
secondary: Person
|
|
9
|
+
offerings:
|
|
10
|
+
- name: Drain clearing
|
|
11
|
+
job: Clears blocked sinks, showers, toilets and main drains, and says what caused the blockage.
|
|
12
|
+
pricing: quote
|
|
13
|
+
- name: Water heater installation
|
|
14
|
+
job: Removes the old tank or boiler and fits, tests and registers a new water heater.
|
|
15
|
+
pricing: fixed
|
|
16
|
+
- name: Emergency call-out
|
|
17
|
+
job: A plumber at the door the same day for a burst pipe, a flood or no water at all.
|
|
18
|
+
pricing: hourly
|
|
19
|
+
- name: Leak detection and repair
|
|
20
|
+
job: Finds the hidden leak behind a wall or under a floor and fixes it with the least damage.
|
|
21
|
+
pricing: quote
|
|
22
|
+
proof:
|
|
23
|
+
- kind: credential
|
|
24
|
+
label: Licensed and insured
|
|
25
|
+
- kind: reviews
|
|
26
|
+
label: Reviews from local customers
|
|
27
|
+
- kind: response-time
|
|
28
|
+
label: Same-day response on emergency calls
|
|
29
|
+
- kind: guarantee
|
|
30
|
+
label: Workmanship guaranteed
|
|
31
|
+
vocabulary:
|
|
32
|
+
problem:
|
|
33
|
+
- burst pipe
|
|
34
|
+
- blocked drain
|
|
35
|
+
- no hot water
|
|
36
|
+
- leaking tap
|
|
37
|
+
- low water pressure
|
|
38
|
+
- water stain on the ceiling
|
|
39
|
+
service:
|
|
40
|
+
- drain clearing
|
|
41
|
+
- water heater install
|
|
42
|
+
- emergency call-out
|
|
43
|
+
- leak repair
|
|
44
|
+
- pipe replacement
|
|
45
|
+
outcome:
|
|
46
|
+
- same-day fix
|
|
47
|
+
- hot water back on
|
|
48
|
+
- no water damage
|
|
49
|
+
- upfront quote
|
|
50
|
+
- tidy job, nothing left behind
|
|
51
|
+
roster:
|
|
52
|
+
- op: add
|
|
53
|
+
route: /emergency
|
|
54
|
+
pageType: BookCall
|
|
55
|
+
reason: Emergency jobs book a call, not a form.
|
|
56
|
+
- op: drop
|
|
57
|
+
route: /pricing
|
|
58
|
+
reason: Most plumbing work is quote-priced, so a price table misleads.
|
|
59
|
+
nav:
|
|
60
|
+
Services: Plumbing services
|
|
61
|
+
content_posture: sample
|
|
62
|
+
intake:
|
|
63
|
+
- id: offerings
|
|
64
|
+
ask: Which of these jobs do you take on, and is there one you do not list? (Drain clearing, water heater installation, emergency call-out, leak detection and repair.)
|
|
65
|
+
fills: [hero, items]
|
|
66
|
+
- id: service_area
|
|
67
|
+
ask: Which towns, postcodes or radius do you cover?
|
|
68
|
+
fills: [hero, cta]
|
|
69
|
+
- id: about
|
|
70
|
+
ask: How did the business start, who does the work, and what will you not take on?
|
|
71
|
+
fills: [content-block]
|
|
72
|
+
- id: proof
|
|
73
|
+
ask: What can a customer check — your licence, your insurance, your reviews, your response time, your guarantee?
|
|
74
|
+
fills: [stats, testimonials, social-proof]
|
|
75
|
+
- id: faq
|
|
76
|
+
ask: What do customers ask before they book, and what do you tell them?
|
|
77
|
+
fills: [faq]
|
|
78
|
+
- id: contact
|
|
79
|
+
ask: How should a customer reach you — phone, form or booking — and how fast do you reply?
|
|
80
|
+
fills: [cta]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Section sources
|
|
2
|
+
|
|
3
|
+
Where each section's copy comes from. **Profile** is tier 1's source;
|
|
4
|
+
**Intake** lists the `intake[]` answers that fill the section at tier 2
|
|
5
|
+
(the same ids appear in each question's `fills`). Fields not named here
|
|
6
|
+
are layout options: keep them as they are.
|
|
7
|
+
|
|
8
|
+
| Section type | Copy fields | Profile (tier 1) | Intake (tier 2) |
|
|
9
|
+
| --------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
10
|
+
| `hero` | `eyebrow`, `headline`, `subheadline`, `primaryCta.label`, `secondaryCta.label` | `vocabulary.problem` + `vocabulary.outcome` for the headline; the category name for the eyebrow | `offerings`, `service_area` |
|
|
11
|
+
| `items` | `headline`, `description`, `items[].title`, `items[].description`, `items[].link` | one item per `offerings[]` entry: `title` = `name`, `description` = `job`, `link.href` = `/services/<kebab name>` | `offerings` |
|
|
12
|
+
| `cta` | `headline`, `description`, `primaryCta.label`, `secondaryCta.label` | `vocabulary.outcome` | `service_area`, `contact` |
|
|
13
|
+
| `content-block` | `eyebrow`, `headline`, `body` | `proof[].label` and `vocabulary.service`, with no names, places or numbers | `about` |
|
|
14
|
+
| `faq` | `headline`, `items[].question`, `items[].answer` | questions in `vocabulary.problem` words; answers from `offerings[].pricing` and `proof[]` | `faq` |
|
|
15
|
+
| `stats` | `headline`, `items[].label`, `items[].value` | `proof[].label` as the label; a value only when it is not a measurement (e.g. `Same day`) | `proof` |
|
|
16
|
+
| `testimonials` | `headline`, `items[].quote`, `items[].author` | quotes in `vocabulary.outcome` words; `author: Sample customer`; no `company`, `rating` or `avatar` | `proof` |
|
|
17
|
+
| `social-proof` | `headline`, labels | `proof[].label` | `proof` |
|
|
18
|
+
|
|
19
|
+
A section type not in this table: fill its copy fields from the profile's
|
|
20
|
+
`vocabulary` and leave its data otherwise as it was. At tier 2 no intake
|
|
21
|
+
answer fills it, so a page carrying it keeps `placeholder: true`.
|
|
22
|
+
|
|
23
|
+
## Frontmatter
|
|
24
|
+
|
|
25
|
+
| Key | Tier 1 | Tier 2 |
|
|
26
|
+
| ---------------------- | -------------------------------------------------- | ------------------------------------------- |
|
|
27
|
+
| `title`, `description` | the category's words; the company name from `_site.mdx` where the page names the company | the company name and the owner's answers |
|
|
28
|
+
| `provider.name` | `siteIdentity.name` | `siteIdentity.name` |
|
|
29
|
+
| `schema.serviceType` | the offering `name` | the offering `name` |
|
|
30
|
+
| `schema.areaServed` | removed | the `service_area` answer |
|
|
31
|
+
| `price`, `priceCurrency`, `availability`, `stripe` | removed | only a price the owner gave, with its currency |
|
|
32
|
+
| `placeholder` | `true` | by the rule in `SKILL.md` Step 2.4 |
|
|
33
|
+
|
|
34
|
+
## Pricing words (Service bodies, FAQ answers)
|
|
35
|
+
|
|
36
|
+
| `pricing` | Say |
|
|
37
|
+
| -------------- | ------------------------------------------------------------------- |
|
|
38
|
+
| `quote` | priced after a look at the job; the quote is given before work starts |
|
|
39
|
+
| `fixed` | one fixed price for the job, agreed before work starts |
|
|
40
|
+
| `hourly` | charged by the hour on site, with the call-out rate stated up front |
|
|
41
|
+
| `subscription` | a regular plan, billed on a schedule |
|
|
@@ -0,0 +1,156 @@
|
|
|
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()`.
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
}
|