agustinkassis/claude-skills

Scaffold a complete email + Nostr-aware waitlist subscription flow into a Next.js project. Generates a Radix-based modal, /api/waitlist routes, NIP-04 encrypted DMs, NIP-44 encrypted waitlist event storage, optional Resend welcome email, and a pluggable destinations layer (Resend Audiences, webhook, Postgres, Supabase, SQLite, or custom). Detects App Router vs Pages Router and emits accordingly. Use when the user says "scaffold waitlist", "add nostr signup", "create subscribe form", "email + nostr waitlist", "early access form", or types `/nostr-waitlist`.

claude-skills とは?

claude-skills is a Claude Code agent skill that scaffold a complete email + Nostr-aware waitlist subscription flow into a Next.js project. Generates a Radix-based modal, /api/waitlist routes, NIP-04 encrypted DMs, NIP-44 encrypted waitlist event storage, optional Resend welcome email, and a pluggable destinations layer (Resend Audiences, webhook, Postgres, Supabase, SQLite, or custom). Detects App Router vs Pages Router and emits accordingly. Use when the user says "scaffold waitlist", "add nostr signup", "create subscribe form", "email + nostr waitlist", "early access form", or types `/nostr-waitlist`.

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agustinkassis/claude-skills/tree/HEAD/skills/nostr-waitlist

Installed? Explore more 生産性&コラボレーション skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Nostr Waitlist Skill

Scaffolds a polished email + Nostr "early access" flow into the host Next.js project. The output is opinionated about UX (Radix dialog, three-state form based on email vs npub vs NIP-05, confetti on success) and unopinionated about destinations — the user picks any combination of bundled adapters (Resend Audiences, webhook, Postgres, Supabase, SQLite) or writes their own satisfying the small Destination interface.

References

  • references/nostr-keys.md — how to generate an nsec, what to put in env
  • references/destination-adapters.md — the Destination interface + a worked example of a custom adapter
  • references/db-schema.sql — canonical waitlist table for Postgres + SQLite
  • references/nip-cheatsheet.md — quick recap of NIP-04 / NIP-05 / NIP-44

Phase 1 — Detect stack

Before asking the user anything, gather facts about the host project.

# Run from the project root the user is currently in
cat package.json | grep -E '"(next|react)"' || echo "NOT_NEXT"
ls app/ 2>/dev/null && echo "HAS_APP"
ls pages/api/ 2>/dev/null && echo "HAS_PAGES_API"
ls tsconfig.json 2>/dev/null && echo "HAS_TS"
ls tailwind.config.ts tailwind.config.js tailwind.config.mjs 2>/dev/null && echo "HAS_TAILWIND"
ls pnpm-lock.yaml yarn.lock package-lock.json bun.lockb 2>/dev/null
ls components/ui/dialog.tsx components/ui/button.tsx components/ui/input.tsx 2>/dev/null

Decide:

  • NOT_NEXT → stop. Tell the user "this skill targets Next.js. Detected: ." and exit.
  • HAS_APP present → use App Router templates (templates/api/app-router/).
  • Only HAS_PAGES_API → use Pages Router templates.
  • Both present → ask the user which to target (Modal vs section can also live in either; route handler placement is what matters).
  • Package manager: pnpm-lock → pnpm, yarn.lock → yarn, package-lock.json → npm, bun.lockb → bun. Default npm if none found.
  • shadcn primitives detected → set EMIT_UI = false. Otherwise EMIT_UI = true.

Phase 2 — Collect scope

Use AskUserQuestion. Batch related questions into a single prompt with ≤4 items each.

Prompt 1 — channels and provider:

  • Which channels? (single) — email / nostr / both (default both)
  • Email provider? (single, only if email enabled) — resend / skip

Prompt 2 — bake-once values:

  • Brand name (free text) — used in email subject, modal copy, DM
  • Sender display + address (free text, only if email enabled) — e.g. Acme <[email protected]>. Baked into lib/resend.ts.
  • Site URL (free text, only if email enabled) — e.g. https://acme.com. Baked into the welcome email template.
  • D-tag (free text, only if nostr enabled) — defaults to <project-slug>-waitlist.

Prompt 3 — destination adapters (multi-select):

none / resend-audience / webhook / postgres / supabase / sqlite

For every adapter chosen, emit its file but leave it commented out in lib/waitlist/destinations.ts. The user uncomments + supplies env when they're ready.

Prompt 4 — UI surface:

modal (default) / inline-section / both

Phase 3 — Install deps

Pick the right package manager command (pnpm add, npm install, yarn add, bun add) and run one install with all packages — running multiple installs back to back is slower and noisier.

Always:

nostr-tools ws @noble/hashes canvas-confetti lucide-react

If EMIT_UI = true:

@radix-ui/react-dialog @radix-ui/react-slot class-variance-authority clsx tailwind-merge

Channel-conditional:

  • Email enabled: resend

Adapter-conditional:

  • postgres adapter selected: postgres
  • supabase adapter selected: @supabase/supabase-js
  • sqlite adapter selected: better-sqlite3

Dev deps in one extra call (only if TypeScript):

@types/canvas-confetti @types/ws
# plus @types/better-sqlite3 if sqlite adapter

Phase 4 — Emit files

For every template file in templates/, read it, replace tokens, and write to the corresponding path in the host project.

Token map

Built once from Phase 1 + Phase 2 answers:

TokenSourceExample
{{BRAND_NAME}}Prompt 2Acme
{{SENDER_NAME}}Prompt 2 (parsed from Sender display)Acme
{{SENDER_EMAIL}}Prompt 2 (parsed from Sender display)[email protected]
{{SITE_URL}}Prompt 2https://acme.com
{{LOGO_URL}}derived from {{SITE_URL}}/logo.svg (or asked)https://acme.com/logo.svg
{{WAITLIST_D_TAG}}Prompt 2acme-waitlist
{{WELCOME_SUBJECT}}derived: Welcome to the {{BRAND_NAME}} waitlist!
{{WELCOME_DM_FIRSTLINE}}derived: Hey — thanks for jumping on the {{BRAND_NAME}} waitlist over Nostr.
{{BRAND_PRIMARY}} / {{BRAND_ACCENT}} / {{BRAND_SUCCESS}} / {{BG}}Phase 2 (or sane defaults)#F5A623 etc.

The literal sentinel __YEAR__ (note: underscores, not braces) is not a scaffold token — it stays in the rendered email template and is substituted at runtime by lib/resend.ts so the copyright year stays fresh year over year. Don't add it to your token map.

For component CSS variables, prefer arbitrary value classes (bg-[var(--brand-primary)]) backed by CSS custom properties the skill inserts into the user's globals.css (offer to insert; don't overwrite).

Output paths (App Router)

lib/nostr.ts                                ← templates/lib/nostr.ts.tpl
lib/resend.ts                               ← templates/lib/resend.ts.tpl                (only if email)
lib/waitlist/types.ts                       ← templates/lib/waitlist/types.ts.tpl
lib/waitlist/destinations.ts                ← templates/lib/waitlist/destinations.ts.tpl
lib/waitlist/run.ts                         ← templates/lib/waitlist/run.ts.tpl
lib/waitlist/adapters/<name>.ts             ← templates/lib/waitlist/adapters/<name>.ts.tpl
                                                                                          (one per selected adapter)
app/api/waitlist/check/route.ts             ← templates/api/app-router/check.route.ts.tpl
app/api/waitlist/subscribe/route.ts         ← templates/api/app-router/subscribe.route.ts.tpl
components/waitlist/waitlist-modal.tsx      ← templates/components/waitlist-modal.tsx.tpl (if modal)
components/waitlist/waitlist-section.tsx    ← templates/components/waitlist-section.tsx.tpl
                                                                                          (if inline-section)
components/waitlist/waitlist-trigger.tsx    ← templates/components/waitlist-trigger.tsx.tpl
components/ui/{dialog,button,input}.tsx     ← templates/components/ui/*.tsx.tpl           (only if EMIT_UI)
templates/waitlist-welcome.html             ← templates/email/waitlist-welcome.html.tpl   (only if email)
templates/nostr-welcome.txt                 ← templates/nostr/nostr-welcome.txt.tpl       (only if nostr)

Output paths (Pages Router)

Same as above, except API routes go to:

pages/api/waitlist/check.ts                 ← templates/api/pages-router/check.api.ts.tpl
pages/api/waitlist/subscribe.ts             ← templates/api/pages-router/subscribe.api.ts.tpl

Append-only .env.example

Read existing .env.example if any. Append (don't overwrite) the keys relevant to the user's choices:

KeyChannel/AdapterNotes
PRIVATE_KEYnostrnsec1… or 64-char hex; instructions in references/nostr-keys.md
RELAY_URLSnostr (optional)comma-sep override; bundled defaults work
RESEND_API_KEYemailhttps://resend.com/api-keys
RESEND_AUDIENCE_IDresend-audience adapterhttps://resend.com/audiences
WEBHOOK_URLwebhook adapterfull https URL
WEBHOOK_SECRETwebhook adapter (optional)HMAC key, hex
DATABASE_URLpostgres adapterpostgres://user:pass@host/db
SUPABASE_URL + SUPABASE_SERVICE_ROLEsupabase adapterservice-role only — never expose client-side
SQLITE_PATHsqlite adapterpath to .db file

Each key gets a one-line # comment above it explaining where to get it.

Phase 5 — Wire trigger

If modal was chosen, point the user at where to mount <WaitlistTrigger /> or <WaitlistModal />. Don't auto-edit pages — show a 5-line snippet they can paste into their hero/header. Suggest the obvious spot (header/hero CTA) based on a quick grep for "early access", "get started", "join", or similar copy in app/page.tsx or pages/index.tsx.

If inline-section was chosen, append (don't overwrite) a <WaitlistSection /> import + render to the most likely landing file, after asking "insert into app/page.tsx after line N? (y/n)".

Phase 6 — Smoke test

Print:

  1. Env-var checklist with where-to-get-it links per key emitted in Phase 4.
  2. Optional dev-server check:
    • "want me to start the dev server and curl /api/waitlist/check? (y/n)"
    • if yes: pnpm dev background, wait 4s, curl -X POST localhost:3000/api/waitlist/check -H 'Content-Type: application/json' -d '{"contact":"[email protected]"}'
    • expect { "hasNip05": false } (a real NIP-05 like [email protected] would return { "hasNip05": true })
  3. Do not auto-call /api/waitlist/subscribe — that triggers real Resend + relay traffic. Tell the user how to test it themselves with their own email.

End with a one-paragraph "what's next": fill .env, mount the trigger, run pnpm tsc --noEmit, deploy.

Notes for the agent running this skill

  • Don't overwrite files without diffing. If lib/nostr.ts already exists, ask first.
  • Append, never replace in .env.example, globals.css, and any page the user already authored.
  • If the user picks email-only, skip every Nostr file — including lib/nostr.ts, the NIP-04 DM template, the npub/nip05 detection branches in the modal. Ship a slimmer modal with a single email field. (Source: templates/components/waitlist-modal.tsx.tpl has a {{NOSTR_BRANCHES}} block that gets replaced with empty string when Nostr is disabled.)
  • The Destination adapters are best-effort. Errors logged, never thrown to the response. The user can change this in lib/waitlist/run.ts if they want hard failures.

関連スキル