Single-source checklist for shipping webfetch v0.1.0 end-to-end. Run sections in order. Every command is copy-pasteable. Rollback notes at the bottom of each irreversible section and consolidated in section 12.
Before starting any section, run:
bash scripts/preflight.shDo not proceed to section 3 until preflight exits 0.
- Pre-launch (async accounts, domains, scopes)
- Secrets setup (GitHub repo secrets)
- First push + OSS launch (npm, Docker, Homebrew via CI)
- Cloud backend deploy (Cloudflare Workers + D1 + KV + R2 + Queues)
- Dashboard deploy (Vercel)
- Landing site deploy (Vercel)
- VS Code extension publish
- Python SDK publish
- MCP registry submissions
- Public launch sequence (HN, X, PH, dev.to, YouTube)
- Post-launch monitoring
- Rollback notes (consolidated)
These can be done asynchronously, in parallel, days before launch day.
# Register getwebfetch.com at your registrar of choice.
# Recommended: Cloudflare Registrar (no markup, free WHOIS privacy).
# After purchase, do nothing with DNS yet — Cloudflare zone setup happens in step 4.npm login
npm whoami
# Verify the @webfetch scope is available:
npm access list packages @webfetch || echo "scope free"
# Reserve by publishing a placeholder if necessary; we will publish real packages in step 3.If @webfetch is taken, decide on alternate scope (e.g. @webfetch-dev) and update every package.json "name" field before continuing.
Create two empty public repos under the ashlrai org:
gh repo create ashlrai/webfetch --public --description "Headless web fetcher with MCP, CLI, and SDKs" --homepage "https://getwebfetch.com"
gh repo create ashlrai/homebrew-webfetch --public --description "Homebrew tap for webfetch"- Sign in at https://dash.cloudflare.com.
- Add
getwebfetch.comas a zone (Websites -> Add a site). - Update nameservers at the registrar to Cloudflare's.
- Create an API token with permissions:
Account.Workers Scripts:Edit,Account.D1:Edit,Account.Workers KV Storage:Edit,Account.Workers R2 Storage:Edit,Account.Queues:Edit,Zone.Workers Routes:Edit(scope to the getwebfetch.com zone). Store asCLOUDFLARE_API_TOKEN.
- Sign in at https://dashboard.stripe.com.
- Open
cloud/shared/pricing.tsand create the 4 products with matching prices (Free, Pro, Team, Enterprise) in live mode. Copy each price ID intocloud/shared/pricing.tsif not already populated. - Get
STRIPE_SECRET_KEY(live modesk_live_...). - Configure a webhook endpoint:
https://api.getwebfetch.com/stripe/webhook(will exist after step 4). Capture the signing secret asSTRIPE_WEBHOOK_SECRET.
- Sign up at https://sendgrid.com.
- Authenticate the
getwebfetch.comsender domain (Settings → Sender Authentication → DKIM + SPF + DMARC records in Cloudflare DNS). - Create an API key (Settings → API Keys → Restricted Access with Mail Send permission only), store as
SENDGRID_API_KEY.
These power the "pooled keys" Pro/Team feature so subscribers don't have to BYOK.
All are optional — a missing key just removes that provider from the pool. Sign
up for the ones you want to include in the pool, then wrangler secret put each
one in section 4.3.
| Key | Sign up at | Used by |
|---|---|---|
PLATFORM_SERPAPI_KEY |
https://serpapi.com | Google Images via SerpAPI |
PLATFORM_UNSPLASH_ACCESS_KEY |
https://unsplash.com/developers | Unsplash photo search |
PLATFORM_PEXELS_API_KEY |
https://www.pexels.com/api | Pexels photo search |
PLATFORM_PIXABAY_API_KEY |
https://pixabay.com/api/docs | Pixabay search |
PLATFORM_BRAVE_API_KEY |
https://api.search.brave.com | Brave Image Search |
PLATFORM_SPOTIFY_CLIENT_ID + _SECRET |
https://developer.spotify.com | Artist + album art |
PLATFORM_FLICKR_API_KEY |
https://www.flickr.com/services/api | CC-licensed photos |
PLATFORM_SMITHSONIAN_API_KEY |
https://api.data.gov | Smithsonian Open Access |
PLATFORM_EUROPEANA_API_KEY |
https://pro.europeana.eu/page/get-api | EU cultural heritage |
The marketing copy promises a "managed browser fallback" on Google Images + Pinterest for paid plans — this is implemented via Bright Data's Web Unlocker REST API.
- Sign up at https://brightdata.com.
- Products → Web Unlocker → create a zone (default name
web_unlocker). - Account settings → API tokens → create a new token with Web Unlocker access.
wrangler secret putin section 4.3:BRIGHTDATA_API_TOKEN— the account-level Bearer tokenBRIGHTDATA_ZONE— only if you renamed your Web Unlocker zone; defaults toweb_unlocker
- Per-workspace daily caps are enforced in code (Pro: 200/day, Team: 1000/day, Enterprise: 5000/day).
npm i -g vercel
vercel login
vercel teams ls # confirm the right team is selectedCapture a token at https://vercel.com/account/tokens for CI as VERCEL_TOKEN.
# npm: https://www.npmjs.com/settings/<user>/tokens -> Granular access token, scope @webfetch, "Read and write".
# GHCR: gh auth token (or PAT with write:packages, read:packages).
# Homebrew tap PAT: a fine-grained PAT with contents:write on ashlrai/homebrew-webfetch.Run from inside the cloned ashlrai/webfetch repo (after step 3.1) — listed here for reference. Replace the trailing string with the real value. Use gh secret set --env <env> instead of repo-level for environment-scoped secrets if preferred.
gh secret set NPM_TOKEN --body "npm_xxx"
gh secret set GHCR_TOKEN --body "ghp_xxx"
gh secret set HOMEBREW_GH_TOKEN --body "github_pat_xxx"
gh secret set CLOUDFLARE_API_TOKEN --body "xxx"
gh secret set CLOUDFLARE_ACCOUNT_ID --body "xxx"
gh secret set STRIPE_SECRET_KEY --body "sk_live_xxx"
gh secret set STRIPE_WEBHOOK_SECRET --body "whsec_xxx"
gh secret set SENDGRID_API_KEY --body "SG.xxx"
gh secret set VERCEL_TOKEN --body "xxx"
gh secret set VERCEL_ORG_ID --body "team_xxx"
gh secret set VERCEL_PROJECT_LANDING --body "prj_xxx"
gh secret set VERCEL_PROJECT_DASH --body "prj_xxx"
gh secret set VSCE_TOKEN --body "xxx" # set after step 7.1
gh secret set PYPI_TOKEN --body "pypi-xxx"Verify:
gh secret listcd ~/Desktop/web-fetcher-mcp
git init
git add .
git commit -m "Initial commit: webfetch v0.1.0"
git branch -M main
git remote add origin git@github.com:ashlrai/webfetch.git
git push -u origin maingh run watch
# Or: open https://github.com/ashlrai/webfetch/actionsRequired workflows: ci.yml, install-test.yml. Do not proceed if any required check is red.
# Confirm version in every package.json + pyproject.toml is 0.1.0.
git tag -a v0.1.0 -m "webfetch 0.1.0"
git push origin v0.1.0Pre-built artifacts available in
dist-release/(built byscripts/preflight.sh/ local prep). To skip the CI round-trip and publish directly:npm publish dist-release/webfetch-core-0.1.0.tgz --access public npm publish dist-release/webfetch-cli-0.1.0.tgz --access public npm publish dist-release/webfetch-mcp-0.1.0.tgz --access public npm publish dist-release/webfetch-server-0.1.0.tgz --access public npm publish dist-release/webfetch-browser-0.1.0.tgz --access publicNote: local tarballs lack npm provenance attestation. Tag-driven CI publish is preferred when provenance matters.
This triggers release.yml (npm publish for each packages/*) and docker.yml (push to ghcr.io/ashlrai/webfetch:0.1.0 + :latest). Watch:
gh run watchnpm view @webfetch/core version
npm view @webfetch/cli version
npm view @webfetch/mcp version
npm view @webfetch/server version
npm view @webfetch/browser version
docker pull ghcr.io/ashlrai/webfetch:0.1.0
docker run --rm ghcr.io/ashlrai/webfetch:0.1.0 --versionThe release.yml job opens a PR against ashlrai/homebrew-webfetch updating Formula/webfetch.rb (templated from homebrew/webfetch.rb). Review and merge:
gh pr list --repo ashlrai/homebrew-webfetch
gh pr merge <num> --repo ashlrai/homebrew-webfetch --squashSmoke test:
brew tap ashlrai/webfetch
brew install webfetch
webfetch --versionAll commands run from cloud/workers/.
cd cloud/workers
npx wrangler login
# D1 database
npx wrangler d1 create webfetch-prod
# -> copy the database_id from the outputEdit cloud/workers/wrangler.toml and paste the database_id into the [[d1_databases]] block (replace any placeholder).
# KV namespaces
npx wrangler kv:namespace create CACHE
npx wrangler kv:namespace create RATE_LIMIT
# -> paste each id into wrangler.toml [[kv_namespaces]] entries
# R2 bucket
npx wrangler r2 bucket create webfetch-artifacts
# Queues
npx wrangler queues create webfetch-jobs
npx wrangler queues create webfetch-jobs-dlqnpx wrangler d1 migrations apply webfetch-prod --remote
# Or, if migrations are stored as plain SQL in cloud/schema/:
npx wrangler d1 execute webfetch-prod --remote --file=../schema/0001_init.sql
npx wrangler d1 execute webfetch-prod --remote --file=../schema/0002_indexes.sql# Required
npx wrangler secret put STRIPE_SECRET_KEY
npx wrangler secret put STRIPE_WEBHOOK_SECRET
npx wrangler secret put SENDGRID_API_KEY
npx wrangler secret put BETTER_AUTH_SECRET # 32+ random bytes
npx wrangler secret put JWT_SIGNING_KEY # 32+ random bytes
# Pooled provider keys (Pro+ feature — set whichever you signed up for in 1.6a)
npx wrangler secret put PLATFORM_SERPAPI_KEY
npx wrangler secret put PLATFORM_UNSPLASH_ACCESS_KEY
npx wrangler secret put PLATFORM_PEXELS_API_KEY
# ...repeat for any other PLATFORM_* keys you have
# Bright Data managed-browser fallback (Pro/Team feature)
npx wrangler secret put BRIGHTDATA_API_TOKEN
# BRIGHTDATA_ZONE is optional — only if you renamed your Web Unlocker zoneGenerate random secrets:
openssl rand -base64 48npx wrangler deploy --env productionIn Cloudflare dashboard -> getwebfetch.com zone -> Workers Routes:
- Pattern:
api.getwebfetch.com/* - Worker:
webfetch-api(or whatevernameis inwrangler.toml)
Or via CLI (already declared in wrangler.toml [[routes]] block, pushed by wrangler deploy).
curl https://api.getwebfetch.com/v1/health
# -> {"ok":true,"version":"0.1.0"}cd cloud/dashboard
vercel link # select ashlrai team, name "webfetch-dashboard"Set environment variables in Vercel project settings (Production scope):
| Key | Value |
|---|---|
NEXT_PUBLIC_API_URL |
https://api.getwebfetch.com |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
pk_live_xxx |
BETTER_AUTH_SECRET |
same as Worker secret |
BETTER_AUTH_URL |
https://app.getwebfetch.com |
DATABASE_URL |
(if dashboard has its own DB connection; otherwise omit) |
Add domain:
vercel domains add app.getwebfetch.com
vercel alias set webfetch-dashboard.vercel.app app.getwebfetch.comDeploy production:
vercel --prodcd cloud/landing
vercel link # select ashlrai team, name "webfetch-landing"
vercel domains add getwebfetch.com
vercel domains add www.getwebfetch.com
vercel --prodVerify OG image is publicly reachable:
curl -I https://getwebfetch.com/og-image.png
# Should be 200 with image/png. If the file is currently SVG, generate a PNG:
# npx @resvg/resvg-js cloud/landing/public/og-image.svg cloud/landing/public/og-image.png --width 1200
# Re-deploy after committing.Test OG rendering at https://www.opengraph.xyz/url/https%3A%2F%2Fgetwebfetch.com.
npm i -g @vscode/vsce
# Create a publisher named "ashlrai" at https://marketplace.visualstudio.com/manage if not done.
# Generate a PAT with scope: Marketplace > Manage at https://dev.azure.com.
vsce login ashlrai # paste the PATcd vscode-extension
vsce package
vsce publish --pat "$VSCE_TOKEN"Pre-built artifact available at
dist-release/webfetch-0.1.0.vsix— publish it directly without rebuilding:npx vsce publish --packagePath dist-release/webfetch-0.1.0.vsix --pat "$VSCE_TOKEN"
Verify at https://marketplace.visualstudio.com/items?itemName=ashlrai.webfetch.
cd packages/sdk-python
poetry config pypi-token.pypi "$PYPI_TOKEN"
poetry build
poetry publishPre-built wheel + sdist available in
dist-release/:python3 -m pip install --user twine TWINE_USERNAME=__token__ TWINE_PASSWORD="$PYPI_TOKEN" \ python3 -m twine upload \ dist-release/webfetch-0.1.0-py3-none-any.whl \ dist-release/webfetch-python-0.1.0.tar.gz
Verify:
pip install webfetch
python -c "import webfetch; print(webfetch.__version__)"Manual PR against https://github.com/mcp-servers/registry. Add an entry under servers/:
gh repo fork mcp-servers/registry --clone
cd registry
# Add servers/webfetch.json with name, description, install command, repo URL.
git checkout -b add-webfetch
git add servers/webfetch.json
git commit -m "Add webfetch"
gh pr create --title "Add webfetch" --body "Headless web fetcher with MCP server, CLI, SDKs."Submit at https://smithery.ai/submit. Provide:
- Repo:
ashlrai/webfetch - Install command:
npx -y @webfetch/mcp - Description: from
packages/mcp/package.json
Submit at https://www.mcp.run/onboard. Same metadata as Smithery.
All times in PT. Stagger to maximize coverage; if HN doesn't pop in the first hour, do not bump.
| Time | Action | Source |
|---|---|---|
| 06:00 | Submit Show HN | cloud/landing/launch/hn-show.md |
| 08:00 | Post X thread | cloud/landing/launch/x-thread.md |
| 10:00 | Launch on Product Hunt | cloud/landing/launch/product-hunt.md |
| Throughout | Reply to every comment within 15 min | — |
| Throughout | Hot-fix bugs as they surface; cut patch releases via git tag v0.1.x |
— |
| 18:00 | Cross-post to dev.to | cloud/landing/launch/devto-post.md |
| Day +1 09:00 | Email 10 YouTubers | cloud/landing/launch/youtube-outreach.md |
# Open: https://news.ycombinator.com/submit
# Title: from hn-show.md
# URL: https://getwebfetch.com
# Text: from hn-show.md (the "first comment" goes in the text field)Schedule the night before at https://www.producthunt.com/posts/new. Set "go live" to 00:01 PT (PH day starts at midnight). Use the gallery, tagline, and first-comment from cloud/landing/launch/product-hunt.md.
Paste each tweet from x-thread.md as a reply chain. Pin the first.
# Tail Worker logs
cd cloud/workers
npx wrangler tail --env production --format pretty
# Tail Vercel landing
vercel logs https://getwebfetch.com --follow
# Tail Vercel dashboard
vercel logs https://app.getwebfetch.com --follow- Cloudflare Workers analytics: https://dash.cloudflare.com -> Workers -> webfetch-api
- Stripe payments: https://dashboard.stripe.com/payments
- npm downloads:
npx npm-stat @webfetch/cli - GitHub stars:
gh api repos/ashlrai/webfetch | jq .stargazers_count
- GitHub Discussions: respond within 1 hour during launch day
- Discord (if configured): pin a "first 24h" announcement
- HN/PH/X comments: 15-minute SLA
Every irreversible action and how to undo it. Keep this section open in a tab on launch day.
# Within 72 hours of publish you can fully unpublish:
npm unpublish @webfetch/cli@0.1.0 --force
# After 72 hours, deprecate instead:
npm deprecate @webfetch/cli@0.1.0 "Use 0.1.1+; see CHANGELOG"
# Then publish a fixed 0.1.1.git push origin :refs/tags/v0.1.0 # delete remote tag
gh release delete v0.1.0 --yes # delete release
git revert <bad-sha> # create revert commit
git tag v0.1.1 && git push origin v0.1.1 # ship the fix# Cannot delete a published tag from GHCR via API in most cases; instead:
docker buildx imagetools create -t ghcr.io/ashlrai/webfetch:latest ghcr.io/ashlrai/webfetch:0.1.1
# Mark the bad tag deprecated in the README and via a release note.cd cloud/workers
npx wrangler rollback --env production
# Or deploy a known-good commit:
git checkout <good-sha> -- src/
npx wrangler deploy --env production
git checkout HEAD -- src/# Every forward migration must have a paired down migration in cloud/schema/.
# To roll back the most recent:
npx wrangler d1 execute webfetch-prod --remote --file=../schema/0002_indexes.down.sqlvercel rollback https://getwebfetch.com
vercel rollback https://app.getwebfetch.comDNS is fully reversible. In Cloudflare dashboard, swap A/CNAME records back to the previous targets. TTL is typically 5 minutes for proxied records.
Stripe products and prices cannot be deleted, only archived. To pull a bad price out of circulation:
- Stripe dashboard -> Products -> select product -> Archive price.
- Update
cloud/shared/pricing.tswith the replacement price ID. - Redeploy Worker + dashboard.
vsce unpublish ashlrai.webfetch@0.1.0
# Then republish a fixed 0.1.1.PyPI does not allow re-uploading the same version. To recover:
# Yank the bad version (still installable by pinning, but hidden from default resolution):
twine yank webfetch -v 0.1.0 --reason "broken release"
# Publish 0.1.1 with the fix.| Window (PT) | On-call | Backup |
|---|---|---|
| 06:00 - 12:00 | Mason | — |
| 12:00 - 18:00 | Mason | — |
| 18:00 - 00:00 | Mason | — |
Update with co-founders/contractors as they come online. Single-operator launch is fine; just block the calendar.