Getting Started
Agent Handoff Prompt
The copy-paste prompt that hands your AI agent a complete task: scaffold a production SaaS, wire GitHub, deploy, and verify each step.
Paste this into your AI agent (OpenCode, Claude Code, Cursor, …) to have it
scaffold a new SaaS project and take it through local setup, GitHub, and
Cloudflare deployment. Replace <Product Name> and <yourdomain.com> first.
You are scaffolding a new SaaS app from the StartupKit template and taking it
through local setup, GitHub repo creation, and Cloudflare deployment. Follow
the steps in order and verify each before continuing.
PREREQUISITES — verify these first and stop if any fail:
- bun --version
- gh auth status (GitHub CLI, authenticated)
- bunx wrangler whoami (Cloudflare access, or ask me for CLOUDFLARE_API_TOKEN)
STEP 1 — SCAFFOLD
- Run: npx startupkit init "<Product Name>" && cd <slugified-name>
- This clones 01-studio/startupkit/templates/repo (private — needs my git
credentials), inits a fresh git history, runs bun install, and installs the
startupkit agent skill to .agents/skills/startupkit/.
STEP 2 — LOCAL ENV (apps/web/)
- cp .env.example .env.local && cp .dev.vars.example .dev.vars
- Generate once: openssl rand -base64 32
- Put the SAME value as BETTER_AUTH_SECRET in both files.
- Verify: bun run typecheck passes; bun run migrate applies 2 migrations;
bun run test passes.
STEP 3 — CLOUDFLARE D1
- cd apps/web && bunx wrangler d1 create startupkit-db
- Put the returned database_id into BOTH wrangler.jsonc and
wrangler.next-dev.jsonc (replace REPLACE_WITH_YOUR_D1_DATABASE_ID).
- Verify: bunx wrangler d1 list shows startupkit-db.
STEP 4 — RUN LOCALLY
- From repo root: bun run dev (app :3000, email preview :3001)
- Verify: GET / returns 200; GET /dashboard returns 307 (auth gate);
GET /kit returns 200 with an X-Robots-Tag: noindex, nofollow header;
GET /robots.txt disallows /kit;
POST /api/auth/email-otp/send-verification-otp with
{"email":"[email protected]","type":"sign-in"} returns 200 — in dev the OTP
prints to the server log instead of being emailed.
STEP 5 — OPTIONAL INTEGRATIONS
- Stripe: create a recurring "Pro" product (monthly + yearly prices); set
STRIPE_SECRET_KEY, STRIPE_PRICE_PRO_MONTHLY, STRIPE_PRICE_PRO_YEARLY,
STRIPE_WEBHOOK_SECRET in apps/web/.env.local; webhook endpoint
https://<yourdomain.com>/api/stripe/webhook for checkout.session.completed
and customer.subscription.*.
- Google OAuth (optional): GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET in
apps/web/.env.local.
- Email: verify a sending domain in Cloudflare Email Service; set EMAIL_FROM
in apps/web/lib/email.ts.
STEP 6 — RENAME THE PRODUCT
- Replace startupkit with the product name: package names in apps/web/package.json,
root package.json script filters, worker name startupkit-web, db name startupkit-db.
- Replace yourdomain.com in apps/web/lib/urls.ts, apps/web/lib/email.ts,
apps/web/wrangler.jsonc routes.
- Update apps/web/public/favicon.svg, apps/web/i18n/messages/en.json,
apps/web/app/page.tsx, and the root README.md.
- Rebrand the listing kit (it ships describing StartupKit itself):
1. apps/web/lib/site.ts — name, url, oneLiner, tagline (≤10 words),
short (≤60 chars), category, model, platform, brandColor (from the new
favicon), socials (only accounts that exist), surfaces to screenshot.
This file also drives the site's <meta description> and OG tags.
2. apps/web/app/kit/page.tsx — rewrite the five long-description tiers
from the product's marketing copy (2–4 short paragraphs each, different
opening sentence per tier, ending with the URL), the Tags: line, the
Submit-next rows, and Still-needed list. Leave Confirmed live listings empty.
3. bun run kit:assets --skip-screenshots (logos + OG + X/LinkedIn/Facebook/
YouTube banners from the new favicon), then with the app running: KIT_BASE_URL=http://localhost:3000
bun run kit:assets --only=screenshots. Commit apps/web/public/kit/.
- Verify: bun run test passes (site.ts constraints are tested); GET /kit
shows no "StartupKit", "startupkit.com", or placeholder text; pasting the
site URL into a link-preview tool shows the new OG image + description.
STEP 7 — GITHUB
- gh repo create <org>/<repo-name> --private --source . --push
- Enable dependabot security updates. Version updates already come from
.github/dependabot.yml in the template, but security updates are per-repo
settings and off by default for private repos:
gh api -X PATCH repos/<org>/<repo-name> \
-f security_and_analysis[dependabot_alerts]=enabled \
-f security_and_analysis[dependabot_security_updates]=enabled
- Verify: gh api repos/<org>/<repo-name> --jq '.security_and_analysis'
reports dependabot_alerts and dependabot_security_updates both "enabled".
- Verify: CI (lint + typecheck + test) is green on the first push.
- gh secret set CLOUDFLARE_API_TOKEN --repo <org>/<repo-name> --body "<API
token with Workers + D1 edit permissions>"
- gh secret set CLOUDFLARE_ACCOUNT_ID --repo <org>/<repo-name> --body "<account
id from bunx wrangler whoami>" (these power .github/workflows/deploy.yml,
which auto-deploys main: D1 migrations first, then the worker version)
STEP 8 — DEPLOY
- cd apps/web
- bunx wrangler secret put BETTER_AUTH_SECRET (same value as Step 2)
- bunx wrangler secret put BETTER_AUTH_URL (https://<yourdomain.com>)
- bun run migrate:remote
- bun run deploy
- Verify: https://<yourdomain.com> returns 200; sign-up works; Stripe webhook
shows 200 deliveries.
- From now on, pushes to main deploy automatically (migrations apply before
the new code ships); the deploy/migrate:remote scripts stay for manual runs.
RULES
- Never commit .env.local or .dev.vars (gitignored).
- Do not push or deploy without showing me the plan first.
- If a verification fails, stop and report — do not improvise fixes.Where this lives
- Source of truth:
docs/agent-create-new-project.md - The scaffolded project also ships its own
README.mdand thestartupkitagent skill (.agents/skills/startupkit/), which agents pick up automatically inside the repo.