Purge for Mac: Free, Open-Source Mac Cleaner
免費開源的 Mac 清理工具,可清理快取、Xcode 垃圾與應用殘留。優先放入廢紙簍,無遙測。可用 Homebrew 安裝,或下載以用於 macOS 13+。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證repo pattern spotter
翻譯準備中
正在載入完整 README。
檢視完整 README ↗A free, email-gated AI reading tool. Visitors answer five questions, pick the hypothesis that lands, and get back a 5-section reading plus product recommendations — one full reading per email address.
/ — placeholder salespage (to be replaced later)/tool — the entire product: email gate → 5 questions → hypothesis
selection → full reading. No login, no accounts.This guide assumes you've never done this before. Follow it in order.
npm install
Copy the example file:
cp .env.local.example .env.local
Then fill in each value in .env.local:
| Variable | Where to get it |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | supabase.com → create a free project → Project Settings → API → "Project URL" |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Same page, "anon public" key. (Not currently used by any client-side code in this app, since there's no login — it's included so the project matches the standard Supabase env var setup, in case you add client-side reads later.) |
SUPABASE_SERVICE_ROLE_KEY | Same page, "service_role" key. Keep this secret — it bypasses all database security rules. It is only ever read inside API routes on the server, never sent to the browser. |
ANTHROPIC_API_KEY | console.anthropic.com → API Keys → Create Key. This key is billed per API call — see the cost note at the bottom of this file. |
ACTIVECAMPAIGN_API_URL | ActiveCampaign → Settings → Developer → "API URL". |
ACTIVECAMPAIGN_API_KEY | Same page, "API Key". Used server-side only, to sync each captured email into ActiveCampaign (tag patternspotter, list Pattern Spotter). The Pattern Spotter list must already exist in ActiveCampaign — create it once in the ActiveCampaign dashboard; the tag is created automatically on first use if missing. If either of these two vars is unset, the sync is silently skipped — it never blocks someone from using the tool. |
Never commit .env.local — it's already in .gitignore.
app/api/generate/route.ts in this project and copy the SQL comment
block at the very top of the file (starts with CREATE TABLE IF NOT EXISTS email_captures).This creates one table, email_captures, which tracks which emails have
already used their free reading (has_completed) and stores the reading
itself as JSON for future reference.
npm run dev
Open http://localhost:3000/tool — that's the
whole product. http://localhost:3000/ is just the placeholder salespage.
.env.local: NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY,
ANTHROPIC_API_KEY, ACTIVECAMPAIGN_API_URL, ACTIVECAMPAIGN_API_KEY.git push to your main branch redeploys automatically./api/generate is the only route that costs money per call (it's the one
Anthropic API request that produces the full reading; /api/hypotheses is
a much cheaper, shorter call). The has_completed flag on email_captures
is what prevents someone from generating more than one free reading per
email address — once it's true for an email, /api/generate returns a 403
without calling Anthropic at all. There's also a simple 20-requests-per-hour
per-IP limit on /api/generate as a backstop against a single source
hammering the tool.
Both PDF paths share the same generator (lib/pdf.ts: title page, styled
sections, a dark CTA page, an about-Beyond-Psychology page), built with
pdfkit — pure JavaScript, no headless browser involved:
/api/generate-pdf, which builds the PDF and streams it straight to the
browser. Nothing is stored for this path./api/generate builds the same PDF, uploads the result
to a private Supabase Storage bucket, and pushes a 30-day signed URL into
an ActiveCampaign custom field for an automation to email out.An earlier version of this rendered the PDF via puppeteer-core +
@sparticuz/chromium (real HTML/CSS, closer to the on-screen design) but
that combination hit two separate Vercel-specific failures in production
(file tracing dropping the bundled Chromium binary, then a missing system
library even after fixing that) and was replaced with this pdfkit version,
which has no native/binary dependency and can't hit that class of bug.
lib/pdf.ts loads the founder photo from public/pdf-assets/myrthe-photo.png
if present, otherwise fetches it from beyondpsychology.eu at render time.
If neither is available, that spot renders as a plain colored circle instead
of failing. Brand fonts (Abril Fatface, Cormorant Garamond italic, Open
Sans) are loaded from public/pdf-assets/fonts/ and registered directly
with pdfkit.
Every completed reading pushes two things to the contact's ActiveCampaign
record, via logReadingTopic in lib/activeCampaign.ts:
pattern-perfectionism-and-overachievement
or pattern-self-sabotage-+-procrastination for a blend. Tags are created
automatically the first time they're used — nothing to set up in AC ahead
of time. Use these to segment your list by pattern for future campaigns.The pattern name itself comes from a new PATTERN_LABEL: line the model
now outputs (see OUTPUT FORMAT in lib/prompts.ts), parsed in
lib/parseReading.ts. It's drawn from the same fixed taxonomy already used
to name the pattern in Section 1 of the reading, so tags stay a small,
consistent set rather than one-off free text.
The tool ships with a full paid-credits system, off by default: while it's off, the tool behaves exactly like the original free, one-reading-per-email version. Nothing changes until you deliberately turn it on.
How it works once turned on: every reading requires a credit. New emails
start at 0 credits — no free reading. The 5 questions and hypothesis
selection are always free to fill in regardless of credits; the paywall only
appears right before the reading itself would be generated (the actual
Anthropic call), when someone has 0 credits at that moment. This is
deliberate — asking for payment after someone has already invested a few
minutes answering the questions converts better than asking upfront. The
paywall (components/tool/BuyAccess.tsx) offers a single pack, configured in
lib/stripe.ts (CREDIT_PACKS): 5 readings for €27. The "X readings left"
count shows on the question form and the reading itself once you have any
credits.
Currency: the charge is always in EUR via Stripe, shown as such everywhere - not localized per visitor.
If someone already answered the questions and picked a hypothesis before
hitting the paywall, their answers + chosen belief are stashed in
localStorage (pattern-spotter:pending-generation, see app/tool/page.tsx)
right before the payment-required attempt. After a successful payment, the
reading is generated immediately from that instead of sending them all the
way back to the first question — the round trip through Stripe's hosted
checkout is a full page navigation, which wipes React state, so this has to
survive in localStorage rather than component state.
Surviving a closed tab, not just a Stripe round trip: separately, as
soon as hypotheses come back, name + email + answers + hypotheses are saved
to localStorage too (pattern-spotter:in-progress-session). If someone
closes the tab while choosing a hypothesis and comes back later, an effect
in app/tool/page.tsx restores that session (re-validating the email via
/api/email-capture first, so credits/payment state stay correct) and
resumes straight at the hypothesis screen instead of sending them all the
way back to the email gate. Cleared the moment a reading is actually shown.
Purely client-side, same as the question-form draft below - never sent to
or stored by us.
Separately, components/tool/QuestionForm.tsx has a "Save my answers as a
text file" link under the submit button, so someone can keep their own copy
of what they wrote - a plain client-side download, not something we store.
Tracking who never converts: anyone who leaves their email but has never
completed a paid reading gets tagged abandoned-before-payment in
ActiveCampaign (tagAbandonedBeforePayment in lib/activeCampaign.ts,
called from app/api/email-capture/route.ts). The tag is removed the moment
they actually complete a paid reading (clearAbandonedBeforePayment, called
from app/api/generate/route.ts), so the segment only ever contains people
who genuinely never converted, not returning customers between purchases.
Build an ActiveCampaign automation on the trigger "tag added:
abandoned-before-payment" (with a wait step, then a check that the tag is
still present before sending) to follow up with this segment. Nothing to
create in ActiveCampaign ahead of time - the tag is created automatically on
first use.
Run this migration once in Supabase's SQL Editor (safe to run anytime, even before turning payments on):
ALTER TABLE email_captures ADD COLUMN IF NOT EXISTS credits_remaining integer not null default 0;
Add these to Vercel's environment variables:
| Variable | Where it comes from |
|---|---|
STRIPE_SECRET_KEY | Stripe Dashboard → Developers → API keys → Standard keys → "Secret key" (sk_live_... for real payments, sk_test_... while testing). Not the "Restricted key" section. |
STRIPE_WEBHOOK_SECRET | Stripe Dashboard → Developers → Webhooks → Add endpoint. URL: https://<your-domain>/api/stripe-webhook. Event to send: checkout.session.completed. After creating it, Stripe shows a "Signing secret" (whsec_...) — that's this value. |
TESTER_COUPON_CODE | Any secret word you pick, e.g. BETATESTER2026. Anyone who enters it in the "Have a code?" field on the email gate gets TESTER_COUPON_CREDITS (5, set in lib/payments.ts) free credits, as if they'd bought the 5-pack — use it to keep letting testers through without charging them. |
PAYMENTS_ENABLED | Leave unset (or false) until you're ready to go live. Set to true and redeploy (Vercel's "Redeploy" button is enough — no code change needed) to flip the whole tool over to paid credits. |
SALE_NOTIFICATION_EMAIL | Optional. Your own inbox — set this to get a "new sale" email each time someone pays. Requires a matching ActiveCampaign field + automation, see below. |
WOOCOMMERCE_API_URL | Optional. https://beyondpsychology.eu — set this (with the two keys below) to also log each sale as a completed order in WooCommerce, see below. |
WOOCOMMERCE_CONSUMER_KEY / WOOCOMMERCE_CONSUMER_SECRET | WooCommerce → Settings → Advanced → REST API → Add key (Permissions: Read/Write). |
Redeploy. PAYMENTS_ENABLED=true is the actual on/off switch; everything
else can be configured ahead of time without affecting live visitors.
lib/payments.ts — the PAYMENTS_ENABLED flag and the coupon-code check.
Every route branches on this flag: unset/false keeps the original
has_completed logic untouched; true switches to credits_remaining.components/tool/BuyAccess.tsx — shown right before generating the reading
when someone has 0 credits, offering the single pack from CREDIT_PACKS.
Buying it calls /api/create-checkout-session with that pack's id,
which creates a Stripe Checkout Session (storing the pack's credit count in
the session's own metadata, not re-derived from price later) and redirects
to Stripe's hosted payment page./api/stripe-webhook — Stripe calls this after a successful payment
(checkout.session.completed) and it adds that pack's credits to that
email (read from the session metadata), fires the Meta Conversions API
Purchase event with the actual amount paid, and (if SALE_NOTIFICATION_EMAIL
is set) pushes a "new sale" notification through ActiveCampaign the same
way the reading PDF gets emailed: create a custom field called
"Pattern Spotter New Sale" in ActiveCampaign, make sure
SALE_NOTIFICATION_EMAIL has a contact there (or let it get created
automatically on the first sale), then build an automation triggered on
that field changing, which emails you the field's value. It also (if
WOOCOMMERCE_API_URL/WOOCOMMERCE_CONSUMER_KEY/WOOCOMMERCE_CONSUMER_SECRET
are set) creates a paid, completed order in WooCommerce via
lib/woocommerce.ts, purely so the sale shows up in the WordPress
dashboard alongside her other orders — no payment actually runs through
WooCommerce, it's a record-only order for the real Stripe charge that
already happened. Get the consumer key/secret from WooCommerce →
Settings → Advanced → REST API on beyondpsychology.eu; WOOCOMMERCE_API_URL
is just the site's base URL (https://beyondpsychology.eu).app/tool/page.tsx reconstructs
everything from the ?checkout=success&email=...&name=... (or
cancelled) query string on the redirect back, then polls
/api/email-capture briefly (the webhook can take a second or two to
land) before continuing.lib/trafficSource.ts captures UTM params, document.referrer, and the
landing URL into sessionStorage on /tool's first load (once per
session, never overwritten by later in-site navigation), and
BuyAccess.tsx sends it along when creating the checkout session. Nothing
is stored server-side — it only ever lands as metadata on the Stripe
Checkout Session and the resulting PaymentIntent, visible directly on
each payment in the Stripe dashboard. Only applies going forward; it
can't retroactively add source data to past payments./review is a standalone page (linked from the bottom of the on-screen
reading, prefilled with the reader's email) where people can leave a star
rating + written review. There's no public review wall — submissions go
straight into a reviews table for you to read and curate, the same way
you already hand-pick testimonials for the salespage.
create table if not exists reviews (
id uuid primary key default gen_random_uuid(),
name text,
email text not null,
rating smallint not null check (rating between 1 and 5),
review_text text not null,
used_on_salespage boolean not null default false,
created_at timestamptz not null default now()
);
reviews table in Supabase's Table
Editor — no separate admin page in the app. Tick used_on_salespage
for any you've copied onto the salespage, so you can tell at a glance
which ones are still unused.SALE_NOTIFICATION_EMAIL. It's reused here as "the owner's
own notification inbox" rather than something sale-specific.Supabase's free tier auto-pauses a project after 7 days with no API/database
activity — visitors get a 500 error until someone notices and manually
restores it in the Supabase dashboard. vercel.json runs a daily Vercel
Cron job that hits /api/keep-alive (a trivial one-row read, retried a few
times on transient failures), which counts as activity and resets that
7-day clock, so a quiet week never takes the tool down silently.
The Vercel Cron alone wasn't reliable enough in practice — the project still
got auto-paused more than once despite it. .github/workflows/keep-alive.yml
adds a second, independent daily ping (GitHub's scheduler instead of
Vercel's, 12 hours apart from the Vercel cron) at the same /api/keep-alive
URL, so one scheduler having an off day doesn't matter.
This is a stopgap. Once real payments are flowing, upgrade the Supabase
project to a paid plan instead — those never auto-pause, and at that point
an unexpected pause is a lot more costly than $25/month. When that happens,
/api/keep-alive, the cron entry in vercel.json, and
.github/workflows/keep-alive.yml can all be deleted.
Members of the separate WordPress/WooCommerce membership site get into the tool for free, bypassing the credits/one-free-reading gate entirely, via a signed link WordPress generates when an active member clicks "Open Pattern Spotter" — not a login, and nothing synced into this tool's own database.
https://patternspotter.beyondpsychology.eu/tool?email=<email>&name=<name>&exp=<unix seconds, now+300>&token=<hex HMAC-SHA256(secret, "email|name|exp")>.MEMBERSHIP_LINK_SECRET is the shared secret — set the same value in
WordPress (wp-config.php on their side) and in this project's Vercel
environment variables./api/membership-login (called once, when /tool loads
with these params) checks that link once, and if it's valid, mints this
tool's own longer-lived session token (2 hours, see
lib/membershipAuth.ts). That second token is what actually travels with
the /api/generate call for the rest of the visit, not the original
WordPress link — both are HMAC-signed, nothing is ever trusted as a bare
client-side flag.app/tool/page.tsx skips the email gate entirely when these link params
are present and verify, and jumps straight to the questions.app/api/generate/route.ts skips both the credits check (paid-credits
mode) and the one-free-reading check (free mode) when a valid session
token is present, and never decrements or writes credits_remaining for
that request.pattern-spotter-member in ActiveCampaign on their
first reading (tagMember in lib/activeCampaign.ts, created
automatically on first use), so the list can be segmented separately from
paying or free-tier visitors.app/
page.tsx salespage placeholder
tool/page.tsx the entire tool (client-side state machine)
api/
email-capture/route.ts stage 1: check/insert email, no AI call
hypotheses/route.ts stage 2→3: 2-3 hypotheses from Anthropic
generate/route.ts stage 3→4: full reading, has_completed check + update
generate-pdf/route.ts on-demand PDF download (pdfkit, not stored)
create-checkout-session/route.ts Stripe Checkout session (paid mode only)
stripe-webhook/route.ts grants credits on successful payment
components/tool/ UI pieces for each stage
components/SiteHeader.tsx logo header, shown on every page via layout.tsx
lib/ prompts, product catalog, supabase/rate-limit/AC/PDF helpers
public/pdf-assets/ fonts + photo used only by the PDF template
Start at app/tool/page.tsx to see how the four stages fit together, then
app/api/generate/route.ts for the core logic (the rate limit, the
has_completed check, the Anthropic call, and the response parsing).