# launch-audit

Adopt this skill: https://proskills.md/skills/other/qianshuixia-shrimpreel-claude-skills-launch-audit

Page: https://proskills.md/skills/other/qianshuixia-shrimpreel-claude-skills-launch-audit
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: .claude/skills/launch-audit/SKILL.md
size: 6927
sha: 7fc8c71df6972ec4ce4c3a12ebfecae5f7aafd00
upstream: https://github.com/qianshuixia/shrimpreel/blob/main/.claude/skills/launch-audit/SKILL.md

````text
---
name: launch-audit
description: 热词站上线前后的只读体检：本地跑 typecheck、test、build、doctor 和 TODO(site)，对线上地址跑 seo:check、test:smoke、移动端性能和截图，再逐项核对只能人工确认的上线清单（站长平台、真实付款退款、发信域名、R2 生命周期、OAuth 等），写一份带证据的报告并给出“能否上线”的结论。用户说“上线检查”“上线前体检”“launch audit”“看看能不能上线”时使用。
---

# 上线体检

目标：一份 `.tmp/launch-audit/<YYYY-MM-DD>/report.md`。每个检查项都写清楚命令、结果、证据位置和待办，最后给出结论。

全程只读：不部署、不跑 `db:migrate:remote`、不 `wrangler secret put`、不提交 IndexNow、不在线上下单，也不打印任何密钥值。需要做这些写操作时，写进报告的待办，由用户确认后再做。

## 0. 准备

- 确定被测地址：默认 `https://<site.domain>`（`src/config/site.ts`）；用户给了 workers.dev 或预发地址就用用户给的。预发站在 smoke 命令里加 `SMOKE_EXPECT=staging`，并跳过 `--production` 和人工清单里只针对正式站的项。
- 建目录 `.tmp/launch-audit/<日期>/`，记录 `git rev-parse HEAD`、`git status --short`，以及 `npx wrangler whoami` 是否已登录（只记账号名）。
- 工作区有未提交改动时，在报告里注明“线上运行的不一定是当前代码”。

## 1. 本地（部署前）

```bash
D=.tmp/launch-audit/<日期>
npm run typecheck 2>&1 | tee $D/typecheck.log
npm test 2>&1 | tee $D/test.log
npm run build 2>&1 | tee $D/build.log
npm run doctor 2>&1 | tee $D/doctor.log          # 未登录 wrangler 时加 -- --offline
git grep -n "TODO(site)" -- src wrangler.jsonc | tee $D/todo-site.log
```

- 用 `--offline` 跑 doctor 时，secret 名字和远端 migration 都没有核对，报告里标成“未验证”，不要写成通过。
- doctor 的错误都是阻塞项。提醒逐条判断，并写明为什么可以接受。
- 检查 `wrangler.jsonc`：绑定了自定义域名后，`workers_dev` 和 `preview_urls` 都应为 `false`。

## 2. 线上（已部署后）

站点还没部署时，这一节整体标成“未执行”，结论最多给到“部署前检查通过”。

```bash
npm run seo:check -- --base https://<domain> --production     # 报告在 .tmp/seo-check/，复制到 $D
SMOKE_BASE_URL=https://<domain> npm run test:smoke 2>&1 | tee $D/smoke.log   # 报告在 .tmp/smoke-prod/
```

- smoke 只发 GET 和必然被拒的 POST，可以对正式站跑。失败时打开 `.tmp/smoke-prod/report/` 查看 trace，并在报告里写明用例名和原因。不要用 `SMOKE_ALLOW_MOCK`（它只给本机预览用）。
- 再用 curl 补几项只读检查，输出存到 `$D/http.log`：
  - `curl -sI http://<domain>/` 应该跳转到 https。
  - `curl -sI https://www.<domain>/` 的结果和预期的主域名策略一致。
  - 配了 `site.seo.indexNowKey` 时，`https://<domain>/<key>.txt` 应返回 200，内容就是 key。
- 移动端性能（可选，有结果就写进报告）：
  - 首选 PageSpeed Insights API：`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=<首页>&strategy=mobile&category=performance&key=$PSI_API_KEY`。不带 key 时用的是共享配额，经常返回 429。
  - 没有 key 时用 `npx lighthouse https://<domain>/ --form-factor=mobile --only-categories=performance --output=json --output-path=$D/lighthouse-mobile.json --chrome-flags="--headless"`。
  - 记录性能分、LCP、CLS、TBT，以及 PSI 返回的真实用户数据（新站通常没有）。
  - 首页移动端性能分低于 70，或 LCP 大于 4 秒，记为待办，不作为阻塞项。
- 截图（可选）：`npx playwright screenshot --device "Pixel 7" --full-page --wait-for-timeout 3000 https://<domain>/ $D/home-mobile.png`（等会话检查完成，否则 CTA 还在转圈）；桌面端去掉 `--device`、加 `--viewport-size "1440,900"`。iPhone 设备默认用 webkit，项目只装了 chromium。中文首页 `/zh` 同样截一张。打开截图看首屏：有没有布局错乱、品牌名、价格和示例视频是不是本站的。

## 3. 人工清单

逐项写成“已确认（证据）”“未确认（怎么确认）”或“不适用”。能只读核对的项自己查，查不到的写成用户的待办，不要猜。

| 项 | 怎么核对 |
| --- | --- |
| Search Console、Bing Webmaster 已验证并提交 sitemap | 问用户，或看 `site.seo` 里的验证码是否已填 |
| IndexNow | key 文件可访问（第 2 节）；`npm run seo:indexnow -- --submit` 由用户决定何时跑 |
| 真实小额付款 + 退款 | 用户用正式店铺买最便宜的积分包，再在后台退款；核对积分到账、回收和两封邮件。不要代替用户下单 |
| 划线原价和评价真实 | `site.packages[].anchorPriceCents` 是真实卖过的原价（欧盟按降价前 30 天内最低价算），不是编出来的高价；页面上的评价、使用人数都有真实来源 |
| 支付商回调地址、收银台商户名 | 对照 README“第三方回调地址”；Dodo 上 live 前商户名改成品牌名 |
| 发信域名已 Onboard、能收到邮件 | 后台 `/admin/emails` 有已发送的记录，或用户收到了付款邮件 |
| 告警邮件能送达 | `ADMIN_EMAILS` 是用户自己的邮箱；后台 `/admin/emails` 能看到告警邮件，或 Workers Logs 里有 `[alert]` |
| R2 生命周期 | `npx wrangler r2 bucket lifecycle list <name>-media`：uploads/ 8 天、outputs/ 31 天，`templates/` 没有规则 |
| R2 模板源视频 | `curl -sI https://<domain>/media/templates/<site.durations[].source>` 每个都返回 200 |
| Turnstile | `TURNSTILE_SITE_KEY` 已填，密钥已由 doctor 核对；用 Cloudflare 控制台确认 hostname 包含本域名 |
| Google OAuth | 同意屏幕已发布（不是 Testing），回调地址已配置；用无痕窗口试一次 Google 登录 |
| 自定义域名与 HTTPS | 第 2 节的 curl 结果；证书有效 |
| 后台概览“上线配置检查” | 管理员登录 `/admin` 看卡片没有问题项（也能查出 `SESSION_SECRET` 太弱） |

## 4. 报告

`report.md` 结构：

1. 结论：
   - **可以上线**：没有阻塞项，人工清单全部确认。
   - **可以上线，有待办**：没有阻塞项，但有未确认的人工项或性能待办。
   - **不能上线**：列出阻塞项。
2. 环境：被测地址、commit、工作区是否干净、wrangler 是否已登录、日期。
3. 各项结果：一张表，列为检查项、命令、结果（通过 / 失败 / 未验证 / 不适用）、证据文件。
4. 阻塞项与待办：每条写清谁来做、怎么做、做完怎么验证。
5. 未验证项及原因。

回复用户时先说结论，再说阻塞项。需要重新部署、迁移或写密钥时，先问用户。
````

## File: README.md
upstream: https://github.com/qianshuixia/shrimpreel/blob/main/README.md

````text
# ShrimpReel

English | [简体中文](README.zh-CN.md)

Built by [浅水虾 (qianshuixia)](https://x.com/qianshuixia2026).

An open-source template for "one photo → an AI video that follows a viral template" SaaS sites. Every trending keyword gets its own site: create a repo from this template, change the config, copy and media, and ship. Everything runs on Cloudflare: Workers + D1 + R2 + KV + Queues + Rate Limiting + Cron Triggers.

https://github.com/user-attachments/assets/ce92074a-2a09-4a3e-b750-4b888d12abf8

A 72-second tour, no sound ([Chinese version](README.zh-CN.md); MP4 files in [docs/media](docs/media)). Screens are from a local run; the demo site "Neon Shuffle AI" and its clips are placeholders.

## Built with ShrimpReel

<a href="https://christmasdanceai.com/?utm_source=github&utm_medium=referral&utm_campaign=shrimpreel"><img src="docs/media/showcase-christmas-dance-ai.jpg" width="600" alt="Christmas Dance AI: one photo turned into an elf dance or a Christmas party dance video"></a>

[Christmas Dance AI](https://christmasdanceai.com/?utm_source=github&utm_medium=referral&utm_campaign=shrimpreel): upload one photo and dance as an elf in Santa's workshop, or at a Christmas party in your own outfit. Two styles, Google sign-in, credit packs and subscriptions, all from this template.

> Status: the full flow works locally (unit tests + Playwright e2e), and the site above runs it in production. The template itself is not deployed; [ROADMAP.md](ROADMAP.md) lists what has and hasn't been verified against real services. Code comments, ROADMAP and `docs/` are currently in Chinese.

- Frontend/SSR: TanStack Start (React 19) + Tailwind 4. English at `/`, Chinese at `/zh`.
- API: Hono (`/api/*`, `/media/*`, `/sitemap.xml`, `/robots.txt`, `/llms.txt`, IndexNow key file); entry point `src/server.ts`.
- Payments: Waffo Pancake (default) or Dodo Payments, both Merchants of Record, or Stripe (you are the merchant and handle sales tax yourself). `mock` is for local dev and e2e only.
- Video: fal.ai queue API (default) or the optional [yir.ai](https://yir.ai/?ref=qianshuixia) router or [NanoAPI](https://nanoapi.ai); `mock` for local dev and e2e. See "Video providers" below.
- Included:
  - Users and credits: Google sign-in (including One Tap), credit packs, monthly and yearly subscriptions and a credit ledger, dashboard (videos, billing, settings, redeem codes, invite link, account deletion, data export); billing shows amounts paid and refunded, orders and the ledger load page by page, and every order has a printable detail page (tax invoices come from the payment provider).
  - Orders and generation: partial and full refunds that claw back credits, a generation queue with a dead-letter queue, credits returned automatically on failure.
    - Try before signing in: the generator keeps the chosen photo, style and length in the browser (IndexedDB, 24 hours) through sign-in and checkout, and tells signed-out visitors their photo is kept and what the cheapest pack covering the chosen length costs. The draft is cleared once a generation is submitted or on sign-out.
    - Upload from phone: on desktop the generator shows a QR code; the phone opens `/phone-upload` (token only in the URL fragment), picks a photo, and the desktop picks it up within a few seconds. The photo is relayed through R2 under `handoff/` (link valid for 10 minutes, one photo per code, swept by the cron after an hour) and is moderated when the desktop uploads it as usual.
  - Growth: first- and last-touch attribution (UTM and ad click IDs), a visit → sign-up → checkout → paid → generate funnel, GA4 events behind a consent banner, public share pages for videos (opt-in per video, with link previews).
  - Admin: overview (including provider cost and gross margin), users, orders, generations, takedowns, contact messages, payment events, emails, audit log, reports and order CSV, redeem codes, referral leaderboard, runtime settings and connection tests.
    - Users: filter by status, role and paying, sort by sign-up, last login or credits; the detail page shows total paid, generation stats, orders, credit grants, the credit ledger and admin actions on that user (granted credits can have an expiry).
    - Orders: search by order id, email or provider transaction id; the detail page merges payment webhooks, credit changes, admin actions and emails into one timeline, with refund, check-with-provider and mark-paid actions.
    - Generations: search by job id, email or provider request id.
  - Transactional email: credits added, video ready, generation failed and refunded, takedown receipts (signed-in users only) and outcomes; admins get new takedowns, deadline reminders, contact messages and payment disputes (Cloudflare Email Service, outbox with dedupe and retries).
  - Compliance and safety: AI-generated label on videos plus an optional (off by default) machine-readable XMP label and free `/verify` page; photos deleted after 7 days and videos after 30; takedown requests on a 48-hour queue (every video made from the same source photo is removed and the photo is blocked); upload moderation and optional frame-by-frame moderation of finished videos; the consent checkbox is recorded server-side; Turnstile on the contact and report forms.
  - Payment disputes: chargebacks claw credits back and alert admins, won disputes give them back, and Stripe early fraud warnings are refunded automatically.
  - Ops and security: maintenance mode, a checkout pause, the payment provider for new orders, a site-wide announcement and a daily generation cap switched from the admin without a deploy; health-check alert emails, instant payment notifications (Telegram, Feishu, Discord and more), daily D1 backups to R2, browser error reporting to Workers Logs, CSP (Report-Only, per-request nonce) and Permissions-Policy.
  - Content and SEO: one-page home (How it works / What is, plus optional problem and "doing it yourself vs. this site" blocks), an extensible article collection (`/guides` hub, related articles), legal pages, contact form; SSR canonical, hreflang, JSON-LD and sitemap; copy is code-split per locale; `npm run seo:check` lints the whole site.

Release notes: [CHANGELOG.md](CHANGELOG.md). Feature notes and plans: [docs/SAAS_VIDEO_FEATURES.md](docs/SAAS_VIDEO_FEATURES.md) and [ROADMAP.md](ROADMAP.md). SEO rules, keyword planning and audit results: [docs/SEO_AUDIT.md](docs/SEO_AUDIT.md). All environment variables and the launch-related `site.ts` fields: [docs/env.md](docs/env.md). Adding a payment provider and the webhook response-code contract: [docs/payments.md](docs/payments.md). The subscription switch, plans and provider setup: [docs/subscriptions.md](docs/subscriptions.md). Conventions to follow before changing code (credits, payment fulfillment, bilingual copy, migrations, e2e): [AGENTS.md](AGENTS.md). The docs/ files and AGENTS.md are in Chinese.

## Local development

```bash
npm install
cp .dev.vars.example .dev.vars       # set at least SESSION_SECRET
npm run seed:local                   # apply D1 migrations and upload the mp4 and jpg files in seed/templates/ to local R2
npm run dev -- --host 127.0.0.1      # http://127.0.0.1:3000
```

- Use npm 11.19 or newer; `devEngines` in `package.json` warns on older versions. Running `npm install` with an older npm rewrites `package-lock.json` into a form that `npm ci` on CI rejects (`Missing: @emnapi/core@… from lock file`).
- `PAYMENT_PROVIDER=mock` and `VIDEO_PROVIDER=mock` by default: buying opens a mock checkout and generating copies the template video. Nothing is charged.
- Outside production the sign-in page has a "dev login" (`POST /api/auth/dev-login`); in production it returns 404. Dev login, mock payments and `/api/dev/*` only accept direct requests from localhost: anything that came through a tunnel or proxy (with `cf-connecting-ip`, `X-Forwarded-For` or similar headers) gets 404. Someone on your LAN can fake those conditions, so don't start the dev server with `--hos
````
