[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7ANPA9683NJSG31HC2F155F":3,"skill-same:01M7ANPA9683NJSG31HC2F155F":71},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":27,"repositoryHtmlUrl":28,"repositoryHomepageUrl":6,"ownerHtmlUrl":29,"ownerBlogUrl":30,"ownerDescription":31,"updatedAt":32,"install":33,"resources":46},"01M7ANPA9683NJSG31HC2F155F","saas-scaffolder",null,"Generates complete, production-ready SaaS project boilerplate including authentication, database schemas, billing integration, API routes, and a working dashboard using Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn\u002Fui, Drizzle ORM, and Stripe. Use when the user wants to create a new SaaS app, start a subscription-based web project, scaffold a Next.js application, or mentions terms like starter template, boilerplate, new project, or wiring up auth and payments.","Cyrilthetecchieee\u002Fcyril-code-","Cyrilthetecchieee","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F219787998?v=4","01M7AF0MMMKRAA0H61XMJPV92H","Julian Cyril D ","01M7AF0MQ10AVG6QJQ1C2BC0F5",2,"MIT","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: \"saas-scaffolder\"\ndescription: \"Generates complete, production-ready SaaS project boilerplate including authentication, database schemas, billing integration, API routes, and a working dashboard using Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn\u002Fui, Drizzle ORM, and Stripe. Use when the user wants to create a new SaaS app, start a subscription-based web project, scaffold a Next.js application, or mentions terms like starter template, boilerplate, new project, or wiring up auth and payments.\"\n---\n\n# SaaS Scaffolder\n\n**Tier:** POWERFUL  \n**Category:** Product Team  \n**Domain:** Full-Stack Development \u002F Project Bootstrapping\n\n---\n\n## Input Format\n\n```\nProduct: [name]\nDescription: [1-3 sentences]\nAuth: nextauth | clerk | supabase\nDatabase: neondb | supabase | planetscale\nPayments: stripe | lemonsqueezy | none\nFeatures: [comma-separated list]\n```\n\n---\n\n## File Tree Output\n\n```\nmy-saas\u002F\n├── app\u002F\n│   ├── (auth)\u002F\n│   │   ├── login\u002Fpage.tsx\n│   │   ├── register\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── (dashboard)\u002F\n│   │   ├── dashboard\u002Fpage.tsx\n│   │   ├── settings\u002Fpage.tsx\n│   │   ├── billing\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── (marketing)\u002F\n│   │   ├── page.tsx\n│   │   ├── pricing\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── api\u002F\n│   │   ├── auth\u002F[...nextauth]\u002Froute.ts\n│   │   ├── webhooks\u002Fstripe\u002Froute.ts\n│   │   ├── billing\u002Fcheckout\u002Froute.ts\n│   │   └── billing\u002Fportal\u002Froute.ts\n│   └── layout.tsx\n├── components\u002F\n│   ├── ui\u002F\n│   ├── auth\u002F\n│   │   ├── login-form.tsx\n│   │   └── register-form.tsx\n│   ├── dashboard\u002F\n│   │   ├── sidebar.tsx\n│   │   ├── header.tsx\n│   │   └── stats-card.tsx\n│   ├── marketing\u002F\n│   │   ├── hero.tsx\n│   │   ├── features.tsx\n│   │   ├── pricing.tsx\n│   │   └── footer.tsx\n│   └── billing\u002F\n│       ├── plan-card.tsx\n│       └── usage-meter.tsx\n├── lib\u002F\n│   ├── auth.ts\n│   ├── db.ts\n│   ├── stripe.ts\n│   ├── validations.ts\n│   └── utils.ts\n├── db\u002F\n│   ├── schema.ts\n│   └── migrations\u002F\n├── hooks\u002F\n│   ├── use-subscription.ts\n│   └── use-user.ts\n├── types\u002Findex.ts\n├── middleware.ts\n├── .env.example\n├── drizzle.config.ts\n└── next.config.ts\n```\n\n---\n\n## Key Component Patterns\n\n### Auth Config (NextAuth)\n\n```typescript\n\u002F\u002F lib\u002Fauth.ts\nimport { NextAuthOptions } from \"next-auth\"\nimport GoogleProvider from \"next-auth\u002Fproviders\u002Fgoogle\"\nimport { DrizzleAdapter } from \"@auth\u002Fdrizzle-adapter\"\nimport { db } from \".\u002Fdb\"\n\nexport const authOptions: NextAuthOptions = {\n  adapter: DrizzleAdapter(db),\n  providers: [\n    GoogleProvider({\n      clientId: process.env.GOOGLE_CLIENT_ID!,\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n    }),\n  ],\n  callbacks: {\n    session: async ({ session, user }) => ({\n      ...session,\n      user: {\n        ...session.user,\n        id: user.id,\n        subscriptionStatus: user.subscriptionStatus,\n      },\n    }),\n  },\n  pages: { signIn: \"\u002Flogin\" },\n}\n```\n\n### Database Schema (Drizzle + NeonDB)\n\n```typescript\n\u002F\u002F db\u002Fschema.ts\nimport { pgTable, text, timestamp, integer } from \"drizzle-orm\u002Fpg-core\"\n\nexport const users = pgTable(\"users\", {\n  id: text(\"id\").primaryKey().$defaultFn(() => crypto.randomUUID()),\n  name: text(\"name\"),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: timestamp(\"emailVerified\"),\n  image: text(\"image\"),\n  stripeCustomerId: text(\"stripe_customer_id\").unique(),\n  stripeSubscriptionId: text(\"stripe_subscription_id\"),\n  stripePriceId: text(\"stripe_price_id\"),\n  stripeCurrentPeriodEnd: timestamp(\"stripe_current_period_end\"),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n})\n\nexport const accounts = pgTable(\"accounts\", {\n  userId: text(\"user_id\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n  type: text(\"type\").notNull(),\n  provider: text(\"provider\").notNull(),\n  providerAccountId: text(\"provider_account_id\").notNull(),\n  refresh_token: text(\"refresh_token\"),\n  access_token: text(\"access_token\"),\n  expires_at: integer(\"expires_at\"),\n})\n```\n\n### Stripe Checkout Route\n\n```typescript\n\u002F\u002F app\u002Fapi\u002Fbilling\u002Fcheckout\u002Froute.ts\nimport { NextResponse } from \"next\u002Fserver\"\nimport { getServerSession } from \"next-auth\"\nimport { authOptions } from \"@\u002Flib\u002Fauth\"\nimport { stripe } from \"@\u002Flib\u002Fstripe\"\nimport { db } from \"@\u002Flib\u002Fdb\"\nimport { users } from \"@\u002Fdb\u002Fschema\"\nimport { eq } from \"drizzle-orm\"\n\nexport async function POST(req: Request) {\n  const session = await getServerSession(authOptions)\n  if (!session?.user) return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 })\n\n  const { priceId } = await req.json()\n  const [user] = await db.select().from(users).where(eq(users.id, session.user.id))\n\n  let customerId = user.stripeCustomerId\n  if (!customerId) {\n    const customer = await stripe.customers.create({ email: session.user.email! })\n    customerId = customer.id\n    await db.update(users).set({ stripeCustomerId: customerId }).where(eq(users.id, user.id))\n  }\n\n  const checkoutSession = await stripe.checkout.sessions.create({\n    customer: customerId,\n    mode: \"subscription\",\n    payment_method_types: [\"card\"],\n    line_items: [{ price: priceId, quantity: 1 }],\n    success_url: `${process.env.NEXT_PUBLIC_APP_URL}\u002Fdashboard?upgraded=true`,\n    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}\u002Fpricing`,\n    subscription_data: { trial_period_days: 14 },\n  })\n\n  return NextResponse.json({ url: checkoutSession.url })\n}\n```\n\n### Middleware\n\n```typescript\n\u002F\u002F middleware.ts\nimport { withAuth } from \"next-auth\u002Fmiddleware\"\nimport { NextResponse } from \"next\u002Fserver\"\n\nexport default withAuth(\n  function middleware(req) {\n    const token = req.nextauth.token\n    if (req.nextUrl.pathname.startsWith(\"\u002Fdashboard\") && !token) {\n      return NextResponse.redirect(new URL(\"\u002Flogin\", req.url))\n    }\n  },\n  { callbacks: { authorized: ({ token }) => !!token } }\n)\n\nexport const config = {\n  matcher: [\"\u002Fdashboard\u002F:path*\", \"\u002Fsettings\u002F:path*\", \"\u002Fbilling\u002F:path*\"],\n}\n```\n\n### Environment Variables Template\n\n```bash\n# .env.example\nNEXT_PUBLIC_APP_URL=http:\u002F\u002Flocalhost:3000\nDATABASE_URL=postgresql:\u002F\u002Fuser:pass@ep-xxx.us-east-1.aws.neon.tech\u002Fneondb?sslmode=require\nNEXTAUTH_SECRET=generate-with-openssl-rand-base64-32\nNEXTAUTH_URL=http:\u002F\u002Flocalhost:3000\nGOOGLE_CLIENT_ID=\nGOOGLE_CLIENT_SECRET=\nSTRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...\nSTRIPE_PRO_PRICE_ID=price_...\n```\n\n---\n\n## Scaffold Checklist\n\nThe following phases must be completed in order. **Validate at the end of each phase before proceeding.**\n\n### Phase 1 — Foundation\n- [ ] 1. Next.js initialized with TypeScript and App Router\n- [ ] 2. Tailwind CSS configured with custom theme tokens\n- [ ] 3. shadcn\u002Fui installed and configured\n- [ ] 4. ESLint + Prettier configured\n- [ ] 5. `.env.example` created with all required variables\n\n✅ **Validate:** Run `npm run build` — no TypeScript or lint errors should appear.  \n🔧 **If build fails:** Check `tsconfig.json` paths and that all shadcn\u002Fui peer dependencies are installed.\n\n### Phase 2 — Database\n- [ ] 6. Drizzle ORM installed and configured\n- [ ] 7. Schema written (users, accounts, sessions, verification_tokens)\n- [ ] 8. Initial migration generated and applied\n- [ ] 9. DB client singleton exported from `lib\u002Fdb.ts`\n- [ ] 10. DB connection tested in local environment\n\n✅ **Validate:** Run a simple `db.select().from(users)` in a test script — it should return an empty array without throwing.  \n🔧 **If DB connection fails:** Verify `DATABASE_URL` format includes `?sslmode=require` for NeonDB\u002FSupabase. Check that the migration has been applied with `drizzle-kit push` (dev) or `drizzle-kit migrate` (prod).\n\n### Phase 3 — Authentication\n- [ ] 11. Auth provider installed (NextAuth \u002F Clerk \u002F Supabase)\n- [ ] 12. OAuth provider configured (Google \u002F GitHub)\n- [ ] 13. Auth API route created\n- [ ] 14. Session callback adds user ID and subscription status\n- [ ] 15. Middleware protects dashboard routes\n- [ ] 16. Login and register pages built with error states\n\n✅ **Validate:** Sign in via OAuth, confirm session user has `id` and `subscriptionStatus`. Attempt to access `\u002Fdashboard` without a session — you should be redirected to `\u002Flogin`.  \n🔧 **If sign-out loops occur in production:** Ensure `NEXTAUTH_SECRET` is set and consistent across deployments. Add `declare module \"next-auth\"` to extend session types if TypeScript errors appear.\n\n### Phase 4 — Payments\n- [ ] 17. Stripe client initialized with TypeScript types\n- [ ] 18. Checkout session route created\n- [ ] 19. Customer portal route created\n- [ ] 20. Stripe webhook handler with signature verification\n- [ ] 21. Webhook updates user subscription status in DB idempotently\n\n✅ **Validate:** Complete a Stripe test checkout using a `4242 4242 4242 4242` card. Confirm `stripeSubscriptionId` is written to the DB. Replay the `checkout.session.completed` webhook event and confirm idempotency (no duplicate DB writes).  \n🔧 **If webhook signature fails:** Use `stripe listen --forward-to localhost:3000\u002Fapi\u002Fwebhooks\u002Fstripe` locally — never hardcode the raw webhook secret. Verify `STRIPE_WEBHOOK_SECRET` matches the listener output.\n\n### Phase 5 — UI\n- [ ] 22. Landing page with hero, features, pricing sections\n- [ ] 23. Dashboard layout with sidebar and responsive header\n- [ ] 24. Billing page showing current plan and upgrade options\n- [ ] 25. Settings page with profile update form and success states\n\n✅ **Validate:** Run `npm run build` for a final production build check. Navigate all routes manually and confirm no broken layouts, missing session data, or hydration errors.\n\n---\n\n## Reference Files\n\nFor additional guidance, generate the following companion reference files alongside the scaffold:\n\n- **`CUSTOMIZATION.md`** — Auth providers, database options, ORM alternatives, payment providers, UI themes, and billing models (per-seat, flat-rate, usage-based).\n- **`PITFALLS.md`** — Common failure modes: missing `NEXTAUTH_SECRET`, webhook secret mismatches, Edge runtime conflicts with Drizzle, unextended session types, and migration strategy differences between dev and prod.\n- **`BEST_PRACTICES.md`** — Stripe singleton pattern, server actions for form mutations, idempotent webhook handlers, `Suspense` boundaries for async dashboard data, server-side feature gating via `stripeCurrentPeriodEnd`, and rate limiting on auth routes with Upstash Redis + `@upstash\u002Fratelimit`.\n","# SaaS Scaffolder\n\n**Tier:** POWERFUL  \n**Category:** Product Team  \n**Domain:** Full-Stack Development \u002F Project Bootstrapping\n\n---\n\n## Input Format\n\n```\nProduct: [name]\nDescription: [1-3 sentences]\nAuth: nextauth | clerk | supabase\nDatabase: neondb | supabase | planetscale\nPayments: stripe | lemonsqueezy | none\nFeatures: [comma-separated list]\n```\n\n---\n\n## File Tree Output\n\n```\nmy-saas\u002F\n├── app\u002F\n│   ├── (auth)\u002F\n│   │   ├── login\u002Fpage.tsx\n│   │   ├── register\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── (dashboard)\u002F\n│   │   ├── dashboard\u002Fpage.tsx\n│   │   ├── settings\u002Fpage.tsx\n│   │   ├── billing\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── (marketing)\u002F\n│   │   ├── page.tsx\n│   │   ├── pricing\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── api\u002F\n│   │   ├── auth\u002F[...nextauth]\u002Froute.ts\n│   │   ├── webhooks\u002Fstripe\u002Froute.ts\n│   │   ├── billing\u002Fcheckout\u002Froute.ts\n│   │   └── billing\u002Fportal\u002Froute.ts\n│   └── layout.tsx\n├── components\u002F\n│   ├── ui\u002F\n│   ├── auth\u002F\n│   │   ├── login-form.tsx\n│   │   └── register-form.tsx\n│   ├── dashboard\u002F\n│   │   ├── sidebar.tsx\n│   │   ├── header.tsx\n│   │   └── stats-card.tsx\n│   ├── marketing\u002F\n│   │   ├── hero.tsx\n│   │   ├── features.tsx\n│   │   ├── pricing.tsx\n│   │   └── footer.tsx\n│   └── billing\u002F\n│       ├── plan-card.tsx\n│       └── usage-meter.tsx\n├── lib\u002F\n│   ├── auth.ts\n│   ├── db.ts\n│   ├── stripe.ts\n│   ├── validations.ts\n│   └── utils.ts\n├── db\u002F\n│   ├── schema.ts\n│   └── migrations\u002F\n├── hooks\u002F\n│   ├── use-subscription.ts\n│   └── use-user.ts\n├── types\u002Findex.ts\n├── middleware.ts\n├── .env.example\n├── drizzle.config.ts\n└── next.config.ts\n```\n\n---\n\n## Key Component Patterns\n\n### Auth Config (NextAuth)\n\n```typescript\n\u002F\u002F lib\u002Fauth.ts\nimport { NextAuthOptions } from \"next-auth\"\nimport GoogleProvider from \"next-auth\u002Fproviders\u002Fgoogle\"\nimport { DrizzleAdapter } from \"@auth\u002Fdrizzle-adapter\"\nimport { db } from \".\u002Fdb\"\n\nexport const authOptions: NextAuthOptions = {\n  adapter: DrizzleAdapter(db),\n  providers: [\n    GoogleProvider({\n      clientId: process.env.GOOGLE_CLIENT_ID!,\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n    }),\n  ],\n  callbacks: {\n    session: async ({ session, user }) => ({\n      ...session,\n      user: {\n        ...session.user,\n        id: user.id,\n        subscriptionStatus: user.subscriptionStatus,\n      },\n    }),\n  },\n  pages: { signIn: \"\u002Flogin\" },\n}\n```\n\n### Database Schema (Drizzle + NeonDB)\n\n```typescript\n\u002F\u002F db\u002Fschema.ts\nimport { pgTable, text, timestamp, integer } from \"drizzle-orm\u002Fpg-core\"\n\nexport const users = pgTable(\"users\", {\n  id: text(\"id\").primaryKey().$defaultFn(() => crypto.randomUUID()),\n  name: text(\"name\"),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: timestamp(\"emailVerified\"),\n  image: text(\"image\"),\n  stripeCustomerId: text(\"stripe_customer_id\").unique(),\n  stripeSubscriptionId: text(\"stripe_subscription_id\"),\n  stripePriceId: text(\"stripe_price_id\"),\n  stripeCurrentPeriodEnd: timestamp(\"stripe_current_period_end\"),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n})\n\nexport const accounts = pgTable(\"accounts\", {\n  userId: text(\"user_id\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n  type: text(\"type\").notNull(),\n  provider: text(\"provider\").notNull(),\n  providerAccountId: text(\"provider_account_id\").notNull(),\n  refresh_token: text(\"refresh_token\"),\n  access_token: text(\"access_token\"),\n  expires_at: integer(\"expires_at\"),\n})\n```\n\n### Stripe Checkout Route\n\n```typescript\n\u002F\u002F app\u002Fapi\u002Fbilling\u002Fcheckout\u002Froute.ts\nimport { NextResponse } from \"next\u002Fserver\"\nimport { getServerSession } from \"next-auth\"\nimport { authOptions } from \"@\u002Flib\u002Fauth\"\nimport { stripe } from \"@\u002Flib\u002Fstripe\"\nimport { db } from \"@\u002Flib\u002Fdb\"\nimport { users } from \"@\u002Fdb\u002Fschema\"\nimport { eq } from \"drizzle-orm\"\n\nexport async function POST(req: Request) {\n  const session = await getServerSession(authOptions)\n  if (!session?.user) return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 })\n\n  const { priceId } = await req.json()\n  const [user] = await db.select().from(users).where(eq(users.id, session.user.id))\n\n  let customerId = user.stripeCustomerId\n  if (!customerId) {\n    const customer = await stripe.customers.create({ email: session.user.email! })\n    customerId = customer.id\n    await db.update(users).set({ stripeCustomerId: customerId }).where(eq(users.id, user.id))\n  }\n\n  const checkoutSession = await stripe.checkout.sessions.create({\n    customer: customerId,\n    mode: \"subscription\",\n    payment_method_types: [\"card\"],\n    line_items: [{ price: priceId, quantity: 1 }],\n    success_url: `${process.env.NEXT_PUBLIC_APP_URL}\u002Fdashboard?upgraded=true`,\n    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}\u002Fpricing`,\n    subscription_data: { trial_period_days: 14 },\n  })\n\n  return NextResponse.json({ url: checkoutSession.url })\n}\n```\n\n### Middleware\n\n```typescript\n\u002F\u002F middleware.ts\nimport { withAuth } from \"next-auth\u002Fmiddleware\"\nimport { NextResponse } from \"next\u002Fserver\"\n\nexport default withAuth(\n  function middleware(req) {\n    const token = req.nextauth.token\n    if (req.nextUrl.pathname.startsWith(\"\u002Fdashboard\") && !token) {\n      return NextResponse.redirect(new URL(\"\u002Flogin\", req.url))\n    }\n  },\n  { callbacks: { authorized: ({ token }) => !!token } }\n)\n\nexport const config = {\n  matcher: [\"\u002Fdashboard\u002F:path*\", \"\u002Fsettings\u002F:path*\", \"\u002Fbilling\u002F:path*\"],\n}\n```\n\n### Environment Variables Template\n\n```bash\n# .env.example\nNEXT_PUBLIC_APP_URL=http:\u002F\u002Flocalhost:3000\nDATABASE_URL=postgresql:\u002F\u002Fuser:pass@ep-xxx.us-east-1.aws.neon.tech\u002Fneondb?sslmode=require\nNEXTAUTH_SECRET=generate-with-openssl-rand-base64-32\nNEXTAUTH_URL=http:\u002F\u002Flocalhost:3000\nGOOGLE_CLIENT_ID=\nGOOGLE_CLIENT_SECRET=\nSTRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...\nSTRIPE_PRO_PRICE_ID=price_...\n```\n\n---\n\n## Scaffold Checklist\n\nThe following phases must be completed in order. **Validate at the end of each phase before proceeding.**\n\n### Phase 1 — Foundation\n- [ ] 1. Next.js initialized with TypeScript and App Router\n- [ ] 2. Tailwind CSS configured with custom theme tokens\n- [ ] 3. shadcn\u002Fui installed and configured\n- [ ] 4. ESLint + Prettier configured\n- [ ] 5. `.env.example` created with all required variables\n\n✅ **Validate:** Run `npm run build` — no TypeScript or lint errors should appear.  \n🔧 **If build fails:** Check `tsconfig.json` paths and that all shadcn\u002Fui peer dependencies are installed.\n\n### Phase 2 — Database\n- [ ] 6. Drizzle ORM installed and configured\n- [ ] 7. Schema written (users, accounts, sessions, verification_tokens)\n- [ ] 8. Initial migration generated and applied\n- [ ] 9. DB client singleton exported from `lib\u002Fdb.ts`\n- [ ] 10. DB connection tested in local environment\n\n✅ **Validate:** Run a simple `db.select().from(users)` in a test script — it should return an empty array without throwing.  \n🔧 **If DB connection fails:** Verify `DATABASE_URL` format includes `?sslmode=require` for NeonDB\u002FSupabase. Check that the migration has been applied with `drizzle-kit push` (dev) or `drizzle-kit migrate` (prod).\n\n### Phase 3 — Authentication\n- [ ] 11. Auth provider installed (NextAuth \u002F Clerk \u002F Supabase)\n- [ ] 12. OAuth provider configured (Google \u002F GitHub)\n- [ ] 13. Auth API route created\n- [ ] 14. Session callback adds user ID and subscription status\n- [ ] 15. Middleware protects dashboard routes\n- [ ] 16. Login and register pages built with error states\n\n✅ **Validate:** Sign in via OAuth, confirm session user has `id` and `subscriptionStatus`. Attempt to access `\u002Fdashboard` without a session — you should be redirected to `\u002Flogin`.  \n🔧 **If sign-out loops occur in production:** Ensure `NEXTAUTH_SECRET` is set and consistent across deployments. Add `declare module \"next-auth\"` to extend session types if TypeScript errors appear.\n\n### Phase 4 — Payments\n- [ ] 17. Stripe client initialized with TypeScript types\n- [ ] 18. Checkout session route created\n- [ ] 19. Customer portal route created\n- [ ] 20. Stripe webhook handler with signature verification\n- [ ] 21. Webhook updates user subscription status in DB idempotently\n\n✅ **Validate:** Complete a Stripe test checkout using a `4242 4242 4242 4242` card. Confirm `stripeSubscriptionId` is written to the DB. Replay the `checkout.session.completed` webhook event and confirm idempotency (no duplicate DB writes).  \n🔧 **If webhook signature fails:** Use `stripe listen --forward-to localhost:3000\u002Fapi\u002Fwebhooks\u002Fstripe` locally — never hardcode the raw webhook secret. Verify `STRIPE_WEBHOOK_SECRET` matches the listener output.\n\n### Phase 5 — UI\n- [ ] 22. Landing page with hero, features, pricing sections\n- [ ] 23. Dashboard layout with sidebar and responsive header\n- [ ] 24. Billing page showing current plan and upgrade options\n- [ ] 25. Settings page with profile update form and success states\n\n✅ **Validate:** Run `npm run build` for a final production build check. Navigate all routes manually and confirm no broken layouts, missing session data, or hydration errors.\n\n---\n\n## Reference Files\n\nFor additional guidance, generate the following companion reference files alongside the scaffold:\n\n- **`CUSTOMIZATION.md`** — Auth providers, database options, ORM alternatives, payment providers, UI themes, and billing models (per-seat, flat-rate, usage-based).\n- **`PITFALLS.md`** — Common failure modes: missing `NEXTAUTH_SECRET`, webhook secret mismatches, Edge runtime conflicts with Drizzle, unextended session types, and migration strategy differences between dev and prod.\n- **`BEST_PRACTICES.md`** — Stripe singleton pattern, server actions for form mutations, idempotent webhook handlers, `Suspense` boundaries for async dashboard data, server-side feature gating via `stripeCurrentPeriodEnd`, and rate limiting on auth routes with Upstash Redis + `@upstash\u002Fratelimit`.\n",{"name":5,"description":7},"main","57e7aa6b6e03bce1a7d7862ba9c2ec312f7f19b5","src\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder",0,"https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee","https:\u002F\u002Fwithcyril.me\u002F","Hi, I’m Julian Cyril D, a Computer Science and Business Systems student at PSNACET.\r\nI’m passionate about full-stack development, UI\u002FUX design, and building sma","2026-11-04 23:28:50",{"source":8,"skillName":5,"commands":34,"prompt":44,"zipAvailable":45},[35,38,41],{"tool":36,"command":37},"npx","npx skills add Cyrilthetecchieee\u002Fcyril-code- --skill saas-scaffolder",{"tool":39,"command":40},"bunx","bunx skills add Cyrilthetecchieee\u002Fcyril-code- --skill saas-scaffolder",{"tool":42,"command":43},"pnpm","pnpm dlx skills add Cyrilthetecchieee\u002Fcyril-code- --skill saas-scaffolder","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder\u002FSKILL.md",true,[47,53,57,61,65],{"path":48,"resourceType":49,"resourceTypeDesc":50,"sizeBytes":51,"sourceHtmlUrl":52},"references\u002Farchitecture-patterns.md","REFERENCE","参考文档",6727,"https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder\u002Freferences\u002Farchitecture-patterns.md",{"path":54,"resourceType":49,"resourceTypeDesc":50,"sizeBytes":55,"sourceHtmlUrl":56},"references\u002Fauth-billing-guide.md",7143,"https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder\u002Freferences\u002Fauth-billing-guide.md",{"path":58,"resourceType":49,"resourceTypeDesc":50,"sizeBytes":59,"sourceHtmlUrl":60},"references\u002Fsaas-architecture-patterns.md",2764,"https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder\u002Freferences\u002Fsaas-architecture-patterns.md",{"path":62,"resourceType":49,"resourceTypeDesc":50,"sizeBytes":63,"sourceHtmlUrl":64},"references\u002Ftech-stack-comparison.md",9028,"https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder\u002Freferences\u002Ftech-stack-comparison.md",{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"scripts\u002Fproject_bootstrapper.py.txt","SCRIPT","脚本",14190,"https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fproduct-team\u002Fskills\u002Fsaas-scaffolder\u002Fscripts\u002Fproject_bootstrapper.py.txt",[72,81,88,96,103,110],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":76,"licenseFileUrl":77,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":19},"01M7AQV6CYX8RMC78420V9WZ62","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","Apache-2.0","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fwebapp-testing\u002FSKILL.md",[],[],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":19},"01M7AQV2895N3S1MEX6K76G6G2","youtube-downloader","Download YouTube videos with customizable quality and format options. Use this skill when the user asks to download, save, or grab YouTube videos. Supports various quality settings (best, 1080p, 720p, 480p, 360p), multiple formats (mp4, webm, mkv), and audio-only downloads as MP3.","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fvideo-downloader\u002FSKILL.md",[],[],{"skillNo":89,"name":90,"nameZh":6,"description":91,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":76,"licenseFileUrl":92,"sourceHtmlUrl":93,"categories":94,"keywords":95,"publishedAt":19},"01M7AQTRNX62DNKVZ5GXEHRBKB","theme-factory","Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors\u002Ffonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftheme-factory\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftheme-factory\u002FSKILL.md",[],[],{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":102,"publishedAt":19},"01M7AQTNNVHAAN9H6DA8T6ZVE0","template-skill","Replace with description of the skill and when Claude should use it.","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftemplate-skill\u002FSKILL.md",[],[],{"skillNo":104,"name":105,"nameZh":6,"description":106,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":109,"publishedAt":19},"01M7AQTJ3MQ6AKX152MEX2MYCC","tailored-resume-generator","Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftailored-resume-generator\u002FSKILL.md",[],[],{"skillNo":111,"name":112,"nameZh":6,"description":113,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":76,"licenseFileUrl":114,"sourceHtmlUrl":115,"categories":116,"keywords":117,"publishedAt":19},"01M7AQTD7D5MYET2YWG7HG9WM3","slack-gif-creator","Toolkit for creating animated GIFs optimized for Slack, with validators for size constraints and composable animation primitives. This skill applies when users request animated GIFs or emoji animations for Slack from descriptions like \"make me a GIF for Slack of X doing Y\".","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fslack-gif-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fslack-gif-creator\u002FSKILL.md",[],[]]