TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Meet people. Stay in touch.
Catch is a mobile-first web app (installable PWA) for people who meet others at in-person events. You scan a LinkedIn QR code, a business card, a digital card or a link, and the person's profile fills itself in. You add a voice note and a priority, and then you follow up while it's still fresh: a ready-made LinkedIn note (you tap send in LinkedIn yourself) or an email draft.檢視完整 README ↗Meet people. Stay in touch.
Catch is a mobile-first web app (installable PWA) for people who meet others at in-person events. You scan a LinkedIn QR code, a business card, a digital card or a link, and the person's profile fills itself in. You add a voice note and a priority, and then you follow up while it's still fresh: a ready-made LinkedIn note (you tap send in LinkedIn yourself) or an email draft.
fra1)npm install
cp .env.example .env.local # DEMO_MODE=true, everything else empty
npm run dev # http://localhost:3000
Demo mode replaces every external call with realistic fixtures. Data lives in .data/demo-kv.json; delete that file to start over. Sign in with any email address (no email is sent); the same address always brings you back to the same demo data, and each address gets its own. Account → Add sample people (or the button on the empty People screen) fills every screen with sample data. Then:
linkedin.com/in/lena-hoffmann-demo is a rich profile.linkedin.com/in/jonas-weber-demo is a thin profile (no about, posts or skills).linkedin.com/in/marco-rossi-demo has only a name and a location.linkedin.com/in/private-anything fails, so you see the "We couldn't read their profile" screen.Other commands:
npm run build && npm start # production build (service worker only registers in production)
npm test # unit tests: grounding, parsers, CSV, Apify mapping, pilot metrics
npm run typecheck
| Part | Demo mode | With keys |
|---|---|---|
| Auth + data | Cookie session, JSON file store | Supabase magic link / Google, Postgres with RLS |
| Profile data | FixtureProvider | ApifyProvider (needs APIFY_TOKEN + APIFY_PROFILE_ACTOR) |
| Card OCR, brief, voice summary, note line, email | Deterministic templates built only from the input | Claude (ANTHROPIC_MODEL, default claude-sonnet-5-5) |
| QR reading, vCard/MECARD parsing, voice transcript, CSV, clipboard handoff, public page, rate limits, deletion | Real | Real |
Each backend switches independently. Supabase is used whenever its URL and anon key are set, even in demo mode. Apify and Anthropic need DEMO_MODE=false and their own keys. Account → the "Demo" card shows which parts are live.
supabase/migrations/ in order, either in the SQL editor or with the CLI (supabase link --project-ref <ref> && supabase db push). They create:
photos storage bucketdelete_expired_people()https://<your-domain>/auth/callback (and http://localhost:3000/auth/callback) to the redirect URLshttps://<project-ref>.supabase.co/auth/v1/callback..env.local (and into Vercel):
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY (server only; it is never sent to the browser)ADMIN_USER_ID, and /admin/pilot opens for you.vercel.json schedules GET /api/cron/cleanup daily. Vercel sends Authorization: Bearer $CRON_SECRET, so set CRON_SECRET in Vercel. The job hard-deletes people whose delete_after has passed, along with their stored photo and cached enrichment. If you'd rather run it in the database, the migration also defines public.delete_expired_people() for pg_cron. That version can't remove storage objects, so prefer the route.
DEMO_MODE=false
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-sonnet-5-5
APIFY_TOKEN=apify_api_...
APIFY_PROFILE_ACTOR=atomus/linkedin-profile-scraper
# optional:
APIFY_PROFILE_INPUT={"profileUrls":["{url}"]}
APIFY_POSTS_ACTOR=
APIFY_SEARCH_ACTOR=
Easiest check: open Account → Apify connection (/admin/apify) on the deployed app. It shows whether the token and actor are accepted, the actor's input fields, and runs one real profile so you can see which fields came back and where from. "Copy as APIFY_FIELD_MAP" gives you an override you can paste into Vercel if a field is read from the wrong key. For lead capture, the basic fields are what matter: name, headline, role, company, location, photo. Leave APIFY_POSTS_ACTOR empty unless you want posts.
Check the Apify field mapping first. The build sandbox could not reach api.apify.com or apify.com, so the actor's input key and output field names in lib/providers/apify-map.ts are unverified candidates. Each field tries a short list of likely keys, and a key that isn't there leaves the field empty; nothing is guessed. To confirm the mapping:
APIFY_TOKEN=... node scripts/apify-inspect.mjs atomus/linkedin-profile-scraper https://www.linkedin.com/in/<someone-who-agreed>
The script prints the actor's input schema and the key names and types of one real output item (not the values). Trim each list in PROFILE_KEYS to the real key, and set APIFY_PROFILE_INPUT if the input key isn't profileUrls.
APIFY_POSTS_ACTOR.APIFY_SEARCH_ACTOR is set (in demo mode it always runs).run-sync-get-dataset-items, which has a 300 s limit. If an actor regularly takes longer, switch to run + webhook.Profiles are cached per user for 30 days (enrichment_cache), so the same URL isn't paid for twice. The raw actor output is kept in people.raw_enrichment for debugging.
localhost only works over USB port forwarding./share, pulls the profile URL out of the shared text and starts the capture pipeline.
text, not url. /share reads all three fields (url, text, title).intent:// link on Android, so the LinkedIn app opens directly. Without the app it falls back to the browser.Vercel runs different routes in different functions, each with its own memory and /tmp, so demo data written by one route (sign-in, a capture) is invisible to another. Without shared storage you sign in and land back on the welcome screen. The sign-up screen warns about this.
KV_REST_API_URL and KV_REST_API_TOKEN (or UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN) automatically.Once Supabase is configured, Redis is no longer used.
Import the repo and add the environment variables from .env.example. vercel.json pins functions to fra1 (Frankfurt) and adds the cleanup cron. Set NEXT_PUBLIC_APP_URL to the production URL; QR codes use the request host when they can.
app/ screens (App Router) and API routes (app/api/*)
(tabs)/ People, Connect, My code, Account (with the tab bar)
scan/ camera, QR (BarcodeDetector -> jsQR fallback), card photo, paste, type
capture/[id]/ loading checklist, likely match, review, can't-read fallback
c/[handle]/ public "Nice to meet you" page (no login)
share/ Android share target
lib/
db/ Repo interface + DemoRepo (JSON) + SupabaseRepo
providers/ ProfileProvider: FixtureProvider, ApifyProvider (+ apify-map)
ai/ Claude calls with fixture fallbacks
grounding.ts starter verification, template fill, completeness (pure, tested)
pipeline.ts source -> normalised input -> enrichment -> people row
supabase/migrations/ schema, RLS, storage bucket, cleanup function
scripts/ icon generation, Apify inspection
NormalizedProfile only. Each conversation starter must cite a non-empty profile field; the server drops any that don't. Card OCR returns null for anything not printed on the card.consent_basis (public profile, card given, self-submitted, or typed by you).usage_events) holds event names, ids and numbers.next/font, so visitors' browsers make no Google Fonts requests.profiles.current_event_id: the event new captures go into.profiles.ai_personalise: the "Personalise with AI" switch on saved notes.people.enrichment_step: drives the loading checklist (polled every 1.2 s).people.website: business cards and vCards carry one.enrichment_cache: the 30-day per-user cache. It's per user so a hard delete never touches another account.connect_requests.person_id: one request per person.