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 application from the StartupKit template and
taking it through local setup, GitHub repo creation, and Cloudflare deployment.
Follow the steps in order. Each step has a verification — do not proceed past a
failed verification.
## Prerequisites
- `bun` installed (`bun --version`)
- `git` configured with the user's credentials
- Cloudflare account access (`bunx wrangler whoami` works, or the user provides
`CLOUDFLARE_API_TOKEN`)
- `gh` CLI authenticated (`gh auth status`) — for repo creation
## 1. Scaffold
```bash
npx startupkit init <Project Name>
cd <project-name-slug>
```
This clones the template from `01-studio/startupkit/templates/repo` (private
repo — requires git credentials), initializes a fresh git history, runs
`bun install`, and installs the `startupkit` agent skill to
`.agents/skills/startupkit/`. A root `AGENTS.md` (with `CLAUDE.md`) ships too —
the Stack guide: our default agent behaviors (how we work, Linear workflow +
etiquette, the git-worktree workflow, SDLC/ADLC) plus a `## StartupKit Stack`
section for this scaffold. Refresh it with `npx startupkit agents --stack`; use
`npx startupkit agents` to install the tool-agnostic default guide in any other
project.
**Stack included:** Next.js 16 App Router, Cloudflare Workers via OpenNext,
better-auth (email OTP + optional Google OAuth) on Cloudflare D1, Stripe
billing (checkout + portal + webhook), transactional email (React Email +
Cloudflare Email binding), shadcn-style UI, Tailwind 4, next-intl, vitest.
## 2. Local environment
```bash
cd apps/web
cp .env.example .env.local
cp .dev.vars.example .dev.vars
```
Generate one secret and put the **same value in both files** as
`BETTER_AUTH_SECRET=`:
```bash
openssl rand -base64 32
```
**Verify:**
```bash
bun run typecheck # passes
bun run migrate # applies 2 migrations to local D1
bun run test # 12 tests pass
```
## 3. Cloudflare D1 (required)
```bash
cd apps/web
bunx wrangler d1 create startupkit-db
```
Copy 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`.
## 4. Run locally
```bash
# repo root
bun run dev
```
- App: http://localhost:3000
- Email template preview: http://localhost:3001
**Verify:** open http://localhost:3000, click "Get started", sign up with an
email — locally the email is NOT sent (console-only fallback); in dev the OTP
is printed to the dev server log. Sign in with it. Dashboard loads.
Also verify the brand kit route is public but unindexed:
```bash
curl -sI http://localhost:3000/brandkit | grep -i "HTTP\|x-robots-tag" # 200 + noindex, nofollow
curl -s http://localhost:3000/robots.txt # Disallow: /brandkit
```
## 5. Optional integrations
### Google OAuth
Set `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` in `apps/web/.env.local`
(OAuth credentials from console.cloud.google.com). Auth degrades to email-OTP
only when unset.
### Stripe billing
1. Create one recurring product ("Pro") in the Stripe dashboard with monthly
and yearly prices.
2. In `apps/web/.env.local` set:
- `STRIPE_SECRET_KEY` (test key for dev)
- `STRIPE_PRICE_PRO_MONTHLY`, `STRIPE_PRICE_PRO_YEARLY`
3. Add a webhook endpoint (Stripe dashboard → Developers → Webhooks):
- URL: `https://<your-domain>/api/stripe/webhook`
- Events: `checkout.session.completed`,
`customer.subscription.created|updated|deleted`
- Copy the signing secret into `STRIPE_WEBHOOK_SECRET`
4. Enable the customer portal in Stripe settings (one-time dashboard action).
**Verify:** Settings page shows upgrade buttons; test checkout with card
`4242 4242 4242 4242` completes and returns to `/settings?upgraded=1`.
### Transactional email
Verify a sending domain in Cloudflare (Email Service) — the `EMAIL` binding is
already declared in both wrangler configs. Set `EMAIL_FROM` in
`apps/web/lib/email.ts` to the verified sender address.
## 6. Rename the product
Search-and-replace across the repo:
- `startupkit` → your product name (package names in `apps/web/package.json`,
root `package.json` script filters, wrangler worker name `startupkit-web`,
D1 name `startupkit-db`)
- `yourdomain.com` → the real domain (`apps/web/lib/urls.ts`,
`apps/web/lib/email.ts`, `apps/web/wrangler.jsonc` route)
- Favicon/copy: `apps/web/public/favicon.svg`, `apps/web/i18n/messages/en.json`,
`apps/web/app/page.tsx`, root `README.md`
### Make the brand kit yours
`/brandkit` (`apps/web/app/brandkit`) is a public, unindexed page with paste-ready
directory copy, brand assets, and a submission tracker. It ships describing
StartupKit itself — overwrite it with the new product:
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), and `surfaces` to
screenshot. This file also drives the root `<meta description>` and OG/Twitter
tags in `app/layout.tsx`.
2. `apps/web/app/brandkit/page.tsx` — rewrite the five long-description tiers from
the product's marketing copy (2–4 short paragraphs, a different opening
sentence per tier so directories don't dedupe them, ending with the URL),
the `Tags:` line, the Submit-next rows, and the Still-needed list. Leave
Confirmed live listings empty until a listing is verified live.
3. Regenerate assets (Chrome headless; committed to `apps/web/public/brandkit/`):
```bash
bun run brandkit:assets --skip-screenshots # logo.svg, logo PNGs, OG, social banners
BRANDKIT_BASE_URL=http://localhost:3000 bun run brandkit:assets --only=screenshots # with the app running
```
**Verify:** `bun run test` passes (`lib/site.test.ts` checks the ≤60/≤10
constraints and rejects placeholders); `GET /brandkit` contains no "StartupKit",
"startupkit.com", `{Product}`, or `TODO`; the site URL pasted into a link-preview
tool shows the new OG image and description.
> **Stack vs Launch:** this scaffold is the free **StartupKit Stack**. Paid
> research and launch tooling (StartupKit Launch) lives outside the scaffold —
> never imply it's included. The default `model`/pricing, tagline, and long
> descriptions describe StartupKit itself and must be replaced during the rebrand,
> or the scaffold ships drift.
## 7. Create the GitHub repo
```bash
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:
```bash
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 workflow (`.github/workflows/ci.yml`) runs green on the first
push (lint + typecheck + test).
Set the Cloudflare secrets so `.github/workflows/deploy.yml` can run (it
auto-deploys `main`: pending D1 migrations first, then the new worker version):
```bash
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>"
```
## 8. Deploy to Cloudflare
```bash
# one-time secrets for production
cd apps/web
bunx wrangler secret put BETTER_AUTH_SECRET # same value as generated above
bunx wrangler secret put BETTER_AUTH_URL # https://<your-domain>
# apply migrations to production D1
bun run migrate:remote
# build + upload + shift traffic to 100%
bun run deploy
```
For a custom domain, uncomment the `routes` block in `apps/web/wrangler.jsonc`
with the real domain and re-run `bun run deploy`.
From now on, pushes to `main` deploy automatically: the workflow applies
pending D1 migrations before the code that reads them ships (`bun run
deploy` / `bun run migrate:remote` stay available for manual runs).
**Verify:** `curl -s -o /dev/null -w "%{http_code}" https://<your-domain>`
returns 200; sign-up flow works against production (emails actually send once
the domain is verified); Stripe webhook shows 200 deliveries.
## Troubleshooting
- `ENEEDAUTH`/404 from npm publish — not applicable here; that's CLI publishing.
- Checkout/`wrangler` 404s — wrong account; re-check `bunx wrangler whoami`.
- Session logged out randomly in dev — `BETTER_AUTH_SECRET` differs between
`.env.local` and `.dev.vars`; they must match.
- `wrangler d1 migrations apply DB --local` fails — run it from `apps/web/`.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.
Agent Guide
Add or refresh a project's AGENTS.md with the startupkit CLI — the default guide for any project, or the StartupKit Stack superset, and how to merge it without clobbering your own conventions.
Domains
Domain intelligence: check availability across TLDs for one name or many, with live registrar pricing.