shrimpreel README

The project's README from github.com/qianshuixia/shrimpreel. As one markdown file: README.md.

ShrimpReel

English | 简体中文

Built by 浅水虾 (qianshuixia).

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; MP4 files in docs/media). Screens are from a local run; the demo site "Neon Shuffle AI" and its clips are placeholders.

Status: the full flow works locally (unit tests + Playwright e2e). It has not been deployed as a production site yet; ROADMAP.md lists what has and hasn't been verified against real services. Code comments, ROADMAP and docs/ are currently in Chinese.

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

Local development

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

Launching a site for a new keyword

Each keyword site is its own repository that tracks this template as upstream, so payment, security and framework fixes are made once in the template and merged into every site.

With Claude Code, once the site repo exists (step 1) you can just say "new hot site: Moon Hop, the moon-jump dance on TikTok". The bundled new-hot-site skill (.claude/skills/new-hot-site/) covers steps 2–4 and the local part of step 7: it writes a site brief, runs the scaffolder, sets pricing, rewrites the copy in both languages, generates placeholder media, then runs typecheck, tests, build, seo:check and an offline doctor. It only touches per-site files and never commits or deploys. Missing information (final domain, licensed media, etc.) is left as TODO(site): … comments; npm run doctor lists them and blocks a production deploy (APP_ENV=production) while any remain.

  1. Create the site repo

    git clone https://github.com/qianshuixia/shrimpreel.git moonhop && cd moonhop
    git remote rename origin upstream
    git remote add origin [email protected]:<owner>/moonhop.git
    

    Don't use GitHub's "Use this template": the new repo shares no history with the template, so git merge upstream/main won't work later. Forks of a public repo can't be private, so site repos usually aren't forks either.

  2. Run the scaffolder

    node scripts/new-site.mjs --keyword "Moon Hop" --domain moonhop.ai --keywords "AI dance video,Moon Hop filter"          # dry run
    node scripts/new-site.mjs --keyword "Moon Hop" --domain moonhop.ai --keywords "AI dance video,Moon Hop filter" --write  # write
    

    It updates the following, then prints a checklist of what still has to be done by hand:

    • site.ts: keyword, brand, domain, email, template.id, article slugs, secondary keywords, publish/update dates; it also clears the previous site's verification tokens, IndexNow key and analytics IDs.
    • wrangler.jsonc: resource names and the ratelimit namespace_id; D1/KV ids are reset to placeholders.
    • package.json: name.
    • Deletes wrangler.staging.jsonc (the template's own staging config).

    Adjust credit packs (packages), the subscription switch and plans (subscriptionsEnabled, plans; set the switch to false to sell packs only), durations and video.falModel in src/config/site.ts as needed. A pack can set anchorPriceCents to show a struck-through regular price and a discount. Use only a price that pack was actually sold at (EU rules use the lowest price in the 30 days before the reduction); new sites usually leave it unset.

  3. Rewrite the copy in src/content/en.ts and src/content/zh.ts. Copy uses variables such as {keyword}. For every keyword, rewrite at least hero, whatIs, ideas, FAQ and both articles, and cover the secondary keywords naturally in the home page body. Rewrite the home page problem and withWithout blocks for the new trend, or set them to null to hide them. Swapping only the keyword across the whole site can be treated by Google as scaled content abuse.

    • Articles: add an entry to articles in site.ts (key, slug, publish and update dates, optional related), then add the same key under articles in every locale's content file; typecheck fails if one is missing. The article lives at /<slug> and is added to /guides, the sitemap and other articles' related links automatically. trend and howTo are linked from the home page; keep them. Aim for at least one pillar page of 1,200+ words plus 4–6 long-tail pages (tutorials, ideas, troubleshooting, comparisons) per site.
    • Copy is code-split per locale, so the browser only downloads the current language (legal pages included). Components call getContent(locale) as usual; only code that needs copy outside the root route and before rendering has to await loadContent(locale) first.
    • Adding a locale: register it in LOCALES and LOCALE_META in site.ts, create src/content/<locale>.ts (same type as en.ts), and add a dynamic import to loaders in src/lib/i18n.ts. Email templates are typed per locale as well, so typecheck lists what else needs translating.
  4. Replace the media

    • seed/templates/<template.id>-<seconds>s.mp4: source videos for generation; upload them to templates/ in R2 after deploying.
    • public/demo/*: home page demo videos and posters. They must be your own or licensed, with the source credited in site.examples.*.credit.
    • public/og.png, public/favicon.svg.
    • node scripts/generate-placeholder-media.mjs (requires ffmpeg) regenerates original placeholder media, named after site.durations and the brand. Delete the old template.id files from seed/templates/ afterwards.
  5. Set vars in wrangler.jsonc: SITE_URL, APP_ENV=production, PAYMENT_PROVIDER, VIDEO_PROVIDER, MODERATION_PROVIDER, TURNSTILE_SITE_KEY, ADMIN_EMAILS, EMAIL_FROM, *_PRODUCTS, GOOGLE_CLIENT_ID; write the D1/KV ids back after creating the resources (see below).

  6. Create products with your payment provider: one one-time product per credit pack and one subscription product per plan, with the product ids in WAFFO_PRODUCTS or DODO_PRODUCTS, e.g. {"starter":"prod_xxx","party":"prod_yyy","squad":"prod_zzz","monthly":"prod_aaa","yearly":"prod_bbb"}. Waffo subscriptions also need WAFFO_STORE_ID for the customer portal; with the subscription switch off, the setup scripts skip plan products. For Waffo, node scripts/waffo-setup.mjs --store STO_xxx --apply creates them in bulk; for Dodo, node scripts/dodo-setup.mjs --apply --write-dev-vars (add --webhook <url> to also create the webhook and write its signing secret to .dev.vars). Prices on the site are display-only; the provider's dashboard is what actually gets charged, so keep them in sync.

  7. Verify locally: npm run typecheck && npm test && npm run test:e2e && npm run seo:check, then deploy (npm run deploy runs the pre-deploy check first, see below).

  8. After launch

    • Run npm run seo:check -- --base https://<domain> --production.
    • Verify the site in Search Console and Bing Webmaster Tools and submit https://<domain>/sitemap.xml.
    • If site.seo.indexNowKey is set, run npm run seo:indexnow -- --submit.
    • With Claude Code, the launch-audit skill runs these checks and the remaining launch checklist in one go (see "Deploying to Cloudflare").

Merging template updates into a site

With Claude Code, just say "sync the template". The sync-upstream skill (.claude/skills/sync-upstream/) prints a sync plan, merges, resolves conflicts by the layers below, fills in local config and runs the full verification. It leaves the merge staged for you to review before committing. If the site doesn't have the skill yet, have it read the template's copy: git show upstream/main:.claude/skills/sync-upstream/SKILL.md (on Windows Git Bash, prefix with MSYS_NO_PATHCONV=1).

By hand:

git fetch upstream
git show upstream/main:scripts/sync-plan.mjs > .tmp/sync-plan.mjs && node .tmp/sync-plan.mjs   # sync plan, read-only
git merge --no-ff upstream/main
npm install && npm run typecheck && npm test && npm run test:e2e
npm run db:migrate:remote   # before deploying, if the template added files to migrations/

Deploying to Cloudflare

Create the resources once per site (names must match wrangler.jsonc):

npx wrangler d1 create <name>                  # put the database_id in wrangler.jsonc
npx wrangler r2 bucket create <name>-media
npx wrangler kv namespace create <name>-kv     # put the id in wrangler.jsonc; titles are unique per account, don't call them all KV
npx wrangler queues create <name>-jobs
npx wrangler queues create <name>-jobs-dlq
npm run db:migrate:remote
npx wrangler r2 object put <name>-media/templates/<file>.mp4 --file seed/templates/<file>.mp4 --content-type video/mp4 --remote

Template staging (workers.dev)

Without a domain, wrangler.staging.jsonc deploys the template itself to https://video-template-staging.<subdomain>.workers.dev to check real bindings, Google sign-in, payment providers in test mode and video provider callbacks.

npx wrangler d1 create video-template-staging                # put the database_id in wrangler.staging.jsonc
npx wrangler r2 bucket create video-template-staging-media
npx wrangler kv namespace create video-template-staging-kv   # put the id in wrangler.staging.jsonc
npx wrangler queues create video-template-staging-jobs
npx wrangler queues create video-template-staging-jobs-dlq
npx wrangler r2 bucket lifecycle add video-template-staging-media expire-uploads uploads/ --expire-days 8
npx wrangler r2 bucket lifecycle add video-template-staging-media expire-outputs outputs/ --expire-days 31
npx wrangler d1 migrations apply DB --remote -c wrangler.staging.jsonc
npx wrangler r2 object put video-template-staging-media/templates/<file>.mp4 --file seed/templates/<file>.mp4 --content-type video/mp4 --remote
npm run doctor -- -c wrangler.staging.jsonc                 # pre-deploy check
npx vite build --mode staging && npx wrangler deploy         # deploy reads the staging config from the build output
node scripts/sync-secrets.mjs -c wrangler.staging.jsonc --file .dev.vars.staging --apply   # or wrangler secret put <NAME> -c wrangler.staging.jsonc one by one
SMOKE_BASE_URL=https://video-template-staging.<subdomain>.workers.dev SMOKE_EXPECT=staging npm run test:smoke   # post-deploy smoke

Third-party callback URLs

Service Where URL
Google OAuth Google Cloud Console → Authorized redirect URIs https://<domain>/api/auth/google/callback
Waffo Pancake Dashboard → Settings → Webhooks https://<domain>/api/webhooks/waffo
Dodo Payments Dashboard → Webhooks https://<domain>/api/webhooks/dodo
fal.ai Added by the code https://<domain>/api/webhooks/fal
yir.ai Added by the code (https URLs only; local dev relies on polling) https://<domain>/api/webhooks/yir

Design notes

Compliance and content safety

Transactional email

Sent directly from the Worker with Cloudflare Email Service (public beta): the send_email binding is EMAIL and the sender is vars.EMAIL_FROM.

Monitoring and alerts

A health check (src/server/alerts.ts) runs on the 5-minute Cron. When a threshold is crossed it emails ADMIN_EMAILS (in Chinese) and writes console.error('[alert] …') to Workers Logs:

Alert Condition Admin page
Generation failure rate In the last hour, ≥ 3 failed jobs and ≥ 30% of finished jobs (excluding manual admin failures and takedowns) /admin/generations
Dead letters A job was failed in the last hour because its queue message ran out of retries /admin/generations
Video provider account In the last hour a job failed because the key is missing or revoked, the yir balance is empty or the key's monthly limit is used up (alerts on the first one) /admin/generations
Backlog A job created more than 30 minutes ago is still unfinished /admin/generations
Payment webhooks A payment webhook failed to process in the last 24 hours /admin/events
Payments to review In the last 24 hours a paid webhook had no matching order, or its amount, currency or product didn't match the order (until the order is marked paid or refunded) /admin/events
Email In the last 6 hours, ≥ 3 emails failed or are still unsent after 3 attempts (alert emails excluded) /admin/emails

Testing

npm run typecheck
npm test            # vitest: Waffo/Dodo signatures and event mapping, refund ratios and refund id rules, attribution parsing, mocks disabled in production, Turnstile and moderation error paths, email templates and retry decisions, alert thresholds and buckets, GA4 not loading before consent and cookie removal on withdrawal, CSP report and browser error summaries and redaction, sign-in redirect validation, PKCE and nonce for Google sign-in, binding accounts by sub and no second sign-up bonus for deleted emails, output download size limits, SQL escaping in D1 backups, payment notification formats and URL redaction, the attribution cookie waiting for consent, 5xx details hidden from non-admins, weak SESSION_SECRET refused in production, launch config check and doctor rules and wrangler output parsing, template sync plan layers, secret sync plan validation and redaction, credit accounting, redeem codes, referral rewards and subscriptions (calendar months, yearly grants, renewals, out-of-order events, aligning the current grant's expiry when the period is corrected, refunds, Dodo/Waffo event mapping) on SQLite, runtime settings validation, cache and daily cap, connection probes and their redaction, the e2e catalog matching the tests
npm run test:e2e    # Playwright: run npm run seed:local first; reuses a dev server already running on 127.0.0.1:3000
E2E_PORT=3200 npm run test:e2e   # use another port when 3000 is taken
npm run seo:check   # needs the dev server running; writes its report to .tmp/seo-check/
SMOKE_BASE_URL=https://<domain> npm run test:smoke   # read-only production smoke, see "Deploying to Cloudflare"
SANDBOX_PROVIDER=dodo E2E_PORT=3200 npm run test:sandbox   # real test-mode checkout (dodo or waffo); needs test credentials in .dev.vars, not run in CI

The risk-ranked e2e acceptance catalog and known gaps are in tests/e2e/CATALOG.md (in Chinese). Update it when you add or rename a test; npm test checks that the two match. For sandbox e2e prerequisites, webhook mode and test cards, see "sandbox e2e" in docs/payments.md (in Chinese).

e2e coverage (64 tests):

Artifacts go to .tmp/video-template-e2e/: report/ (HTML report with screenshots and traces for every step), results.json and test-results/.

Project layout

src/config/site.ts      per-site config
src/content/            English and Chinese copy (one chunk per locale)
src/routes/             pages ({-$locale} is the optional locale prefix, admin/ is the admin)
src/server/             API: auth, credits, orders, payments/, video/, jobs (queue, dead letters, Cron), alerts, admin, seo, retention, takedown, moderation, turnstile, email (outbox), notifications (events → email), settings (runtime settings), probes (connection tests), csp, client-error
migrations/             D1 migrations
containers/postprocess/ post-processing container: burned-in watermark, C2PA signing, cover and preview (experimental, not wired into the Worker yet; see ROADMAP)
seed/templates/         source videos for generation
.claude/skills/         Claude Code skills: new-hot-site (launch a keyword site), sync-upstream (merge template updates), launch-audit (launch checks)
scripts/                new-site, doctor (pre-deploy checks), sync-plan (template sync plan), sync-secrets (secret sync), seo-check, indexnow, waffo-setup, dodo-setup, webhook-proxy, seed-local, placeholder media
tests/                  unit, e2e (mock providers), smoke (post-deploy), sandbox (payment provider test mode)

License

MIT

shrimpreel (qianshuixia/shrimpreel)

Searching Database…

Open-source template for "one photo → AI video that follows a viral template" SaaS sites, one site per trending keyword. ai video generation, admin console.

Size
12.8 MB
Stars
211
Updated
CommitOct 21
Age
3d
Commits · 90 Days