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,即可验证所有权并自行管理介绍。
一分钟完成验证AI resume-to-portfolio SaaS (BYOK) — Portfol.io
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Shipfolio — AI Developer Portfolio Builder SaaS
Hosted product: students turn a resume into a live portfolio site either by
prompting a recruiter-grade AI (their own API key) or editing structured fields directly,
then publish it to their own Vercel account.
This repo is the product — accounts, per-user storage, the BYOK
extraction endpoint, and the Vercel deploy pipeline. It is separate from
surya-vamshi-portfolio,
which stays Surya's own single-tenant personal site (Lovable-synced,
deployed to Cloudflare) and is untouched by this project.查看完整 README ↗Turn a resume into a bespoke, model-written HTML portfolio website in minutes. Free preview with watermarked studio inspection. One payment ($9, or ₹499 in India) unlocks Pro (clean HTML, zip export, GitHub push, Vercel deployment) and includes 1 year of hosting with a custom domain; hosting then renews at $7.99/year (₹449). See Payments & Billing. No fixed templates or JSON steps — each portfolio is an individual, self-contained HTML/CSS document written by an LLM following strict guardrail rules and verified by automated fact checking.
Clone the repository and install dependencies:
npm ci
Configure environment variables:
cp .env.example .env
Fill in your Supabase credentials (SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY), a 32-byte base64 ENCRYPTION_KEY, and APP_ORIGIN (e.g. http://localhost:3000).
Database migrations:
Migrations live in supabase/migrations/ (0001 through 0021). In local development, apply them via the Supabase CLI (supabase db push) or SQL editor. See Deploying for strict production migration rules.
Start the local development server:
npm run dev
| Script | Command | Purpose |
|---|---|---|
npm run dev | vite dev | Start local Vite development server |
npm run build | vite build | Create production client and SSR build |
npm run preview | vite preview | Preview production build locally |
npm test | vitest run | Run unit and integration tests |
npm run lint | eslint . | Run ESLint across codebase |
npm run format | prettier --write . | Format all source files with Prettier |
npm run setup:retention | node scripts/setup-retention-cron.mjs | Configure Supabase storage purge cron & Edge Function |
Before committing or opening a PR, ensure all quality gates pass:
# Typecheck
npx tsc --noEmit -p .
# Linting (must have 0 errors)
npm run lint
# Test suite
npx vitest run
# Production build
npm run build
All variables are read inside handlers/functions at runtime, never at module scope.
| Variable | Required | Purpose & Rules |
|---|---|---|
SUPABASE_URL | Yes | Supabase project URL (server-side) |
SUPABASE_ANON_KEY | Yes | Supabase anonymous API key (server-side) |
SUPABASE_SERVICE_ROLE_KEY | Yes | Supabase service-role admin key (privileged server-side operations) |
VITE_SUPABASE_URL | Yes | Supabase project URL exposed to the browser for Auth SDK |
VITE_SUPABASE_ANON_KEY | Yes | Supabase anonymous API key exposed to the browser for Auth SDK |
ENCRYPTION_KEY | Yes | 32 random bytes, base64-encoded. Used for envelope encryption (AES-256-GCM) of BYOK Gemini keys and Vercel OAuth access tokens |
APP_ORIGIN | Yes | Primary public URL of the application (e.g. https://your-app.vercel.app or https://www.shipfolio.xyz). Used for CSRF Origin checks and OAuth callback URLs |
APP_EXTRA_ORIGINS | No | Comma-separated list of additional permitted request origins (e.g. https://www.shipfolio.xyz) when APP_ORIGIN holds the Vercel app domain |
SITES_DOMAIN | No | Shared base domain for user-hosted portfolio sites (e.g. shipfolio.xyz). Subdomain sites resolve at https://{slug}.${SITES_DOMAIN} |
SITES_BASE_URL | No | Path-based hosting base URL (e.g. https://www.shipfolio.xyz/s). When set, siteUrl() builds <base>/<slug> instead of <slug>.<SITES_DOMAIN>, serving portfolios from /s/<slug> on the main domain when wildcard DNS is unavailable |
CRON_SECRET | Yes | Bearer token secret expected in the Authorization: Bearer <CRON_SECRET> header for Vercel Cron routes (/api/cron/free-queue, /api/cron/domains, /api/cron/hosting) |
OPENROUTER_API_KEY | No (fallback) | API key for OpenRouter :free models, used after Groq. OpenRouter's free models share one account-wide cap (50 requests a day unless the account has ever bought $10 of credit), so the four slots default to 12 calls each () |
A free model cannot write a whole page in one call (a few tokens a second, small per-minute limits, our own call limits). So the free source builds the page in small saved steps (src/server/generation/pipeline/):
default-design.ts, so text is always readable and the page always responsive. The model chooses; it never writes colour rules.Every step has a fallback made in code, so a build always finishes. State is saved after each step in generation_jobs.params.pipeline (with a log of which model did what). A run lasts about 170 seconds, then hands over to a fresh invocation (/api/cron/generation-advance); the status poll restarts a build that stalled. The Gemini-key source is unchanged (one call). The user's profile photo is always put in the hero by code if the model leaves it out; reference screenshots are not used (free models cannot see images), and the free-option screen says so. The user's regenerate notes reach the hero and every section, and their typed "specifics" reach the hero and About.
Studio regenerate follows the method that built the page (src/lib/regen-method.ts: the user's choice in this browser session, else the source of the latest generation job, else free). The panel offers Free models / My Gemini key / Claude or ChatGPT. Claude and ChatGPT run in the user's own chat, so that choice shows a handoff (McpHandoff): a ready-made prompt (they call get_portfolio_html, then save_portfolio_html) with Copy, "Open Claude" and "Open ChatGPT" links that pre-fill it, and the Studio polls /api/content and refreshes (with the popup and chime) when the chat saves a new version.
Providers are tried best first: Groq's models, then OpenRouter's :free models. Typical build on Groq: about 20-30 seconds. The progress screen shows "Step N of M"; the user can close the page. When a build finishes (free, Claude/ChatGPT, or a Studio regeneration) src/lib/build-notify.ts shows a popup (BuildReadyPopup, mounted at the root), plays a two-note chime made with the Web Audio API, and, while the tab is in the background, blinks the tab title, puts a green dot on the tab icon and sends a desktop notification if the user allowed it. It fires once per build, and the progress screen has an on/off control (remembered in localStorage).
Payments run on Dodo Payments (Merchant of Record), live since 2026-10-15 (go-live record: docs/PAYMENTS_GO_LIVE.md). One checkout buys everything.
The offer
| Outside India | India | |
|---|---|---|
| Pay today (Pro + 1 year of hosting) | $9 | ₹499 (UPI and cards) |
| Hosting renewal from year 2 | $7.99 / year, renews automatically | ₹449 / year, renewed by the user (India pays once, no UPI/card mandate) |
| Tax | included in the price | included (GST) |
| Refunds | full refund within 14 days of any payment (policy, issued manually in the Dodo dashboard) | same |
How it is built
x-vercel-ip-country header (src/server/geo.ts). India gets the INR product (UPI only works in INR), everyone else the USD product. Display prices live in src/config/pricing.ts; GET /api/billing/pricing serves them to the UI.DODO_PRODUCT_ID_PRO) is that subscription. India uses a one-time product (DODO_PRODUCT_ID_PRO_IN, ₹499) because UPI AutoPay mandates show a ₹15,000 limit and a 2050 end date that cannot be changed; the webhook turns a payment without a subscription id into a 365-day hosting term (applyOneTimeHosting). A Pro user whose hosting lapsed renews through two renewal-only products (DODO_PRODUCT_ID_HOSTING recurring USD, _IN one-time INR). POST /api/billing/checkout picks the right product on the server.users.plan (free / pro, set by the first payment and kept for good), purchases (audit log; kind = pro or hosting_renewal; refunded_at), hosting_subscriptions (one row per user; hosting is active while now() < grace_until), webhook_events (Standard Webhooks ids, so a redelivery is a no-op).hasPaidAccess (Pro: downloads, exports) and hasHostingAccess (publishing and custom domains, src/server/billing.ts). Both pass when PAYMENTS_ENABLED=false and for admins.POST /api/webhooks/dodo): verifies the signature, then handles payment.succeeded (first payment unlocks Pro, later ones are recorded as renewals), subscription.active|renewed|on_hold|cancelled|expired|failed (keeps the hosting term), and refund.succeeded / dispute.lost / dispute.accepted (drops the user to free, ends hosting, cancels the Dodo subscription, unpublishes the site). Events for other products and partial refunds are ignored. A new subscription for a user cancels their older one, so a lapsed-then-renewed user is never charged twice.Environment: see the DODO_* and PAYMENTS_ENABLED rows in Environment Variables. Vercel gotcha: the variables already exist, so use vercel env update NAME production --scope <team> --yes, not add.
Going live (done 2026-10-15; steps kept for reference, details in docs/PAYMENTS_GO_LIVE.md)
https://<app>/api/webhooks/dodo) with the events above and copy its signing secret.DODO_ENVIRONMENT=live_mode, DODO_PAYMENTS_API_KEY, DODO_WEBHOOK_SECRET and the four product ids on Vercel, then redeploy.hosting_subscriptions, purchases), and only then set PAYMENTS_ENABLED=true.Test mode notes: India cards complete through a gateway simulator ("Simulate Success"); US cards use 4242 4242 4242 4242. Dodo test refunds can fail with Insufficient funds in wallet until the test balance settles.
Shipfolio runs on Vercel with a paired Supabase backend. Production deployment spans two Vercel projects, scheduled cron triggers, and strict database migration rules.
.).https://www.shipfolio.xyz or https://your-app.vercel.app).src/routes/**), user authentication, Studio editing, background generation jobs, OAuth endpoints (/authorize, /token, /register), MCP Streamable HTTP (/mcp), and path-based portfolio serving at /s/<slug>.shipfolio-sites Projectsites-app/.sites-app/vercel.json to route all incoming requests /(.*) to /api/serve.janedoe.com). When a user attaches their custom domain and passes DNS TXT verification, the domain is added to this Vercel project via the Vercel REST API (addDomain).sites storage bucket with an opaque-origin Content Security Policy (sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox), preventing user scripts from interacting with app credentials.vercel.json):
/api/cron/free-queue (scheduled at 10 0 * * *): Processes queued free generations once daily provider budgets reset./api/cron/domains (scheduled at 20 0 * * *): Cleans up unverified pending custom domain claims older than 14 days./api/cron/hosting (scheduled at 30 0 * * *): Unpublishes sites and detaches custom domains for users whose hosting lapsed (past the 14-day grace), and prunes processed webhook ids older than 30 days.Authorization: Bearer <CRON_SECRET> header.pg_cron & Edge Functions:
purge-post-generation-uploads (hourly): Deletes raw resumes and visual references 48 hours after generation.purge-stale-draft-uploads (daily at 03:00): Deletes photos and project images for drafts inactive after 30 days.queue-stale-draft-warnings (daily at 04:00): Flags warning notifications at day 27–28.[!CAUTION] Database migrations are NEVER applied automatically by developers or CI pipelines.
supabase/migrations/ is reviewed line-by-line, explained, and approved before execution.After every push, check production serves the new code before applying a migration.
portfolio-builder-saas/
├── docs/ # Architecture, handoff specs, and rules
│ ├── ARCHITECTURE.md # Pipeline architecture, security boundary & table rules
│ ├── handoffs/ # Sequential agent task specs and reports
│ └── userflow.md # End-to-end current and target user journeys
├── scripts/ # Operational utility scripts
│ ├── hash-admin-password.mjs # Generate scrypt hash for ADMIN_PASSWORD_HASH
│ └── setup-retention-cron.mjs # Provision storage purge Edge Function & secrets
├── sites-app/ # Standalone Vercel project for user custom domains
│ ├── api/serve.ts # Edge server serving sandboxed custom domain portfolios
│ ├── lib/ # Shared domain & CSP verification logic
│ └── vercel.json # Rewrite rules for custom domain routing
├── src/
│ ├── components/ # UI components (shadcn/radix primitives, studio, wizard, layout)
│ │ ├── auth/ # Authentication guards and forms
│ │ ├── byok/ # BYOK key management components
│ │ ├── hero/ # Landing page hero, video, and visual showcase
│ │ ├── layout/ # Navbar, footer, logos, shell layouts
│ │ ├── preview/ # Sandboxed iframe portfolio preview
│ │ ├── publish/ # Publish toolbar, domain settings, and dialogs
│ │ ├── settings/ # Account, connector, and custom domain settings
│ │ ├── ui/ # Base design system primitives (button, dialog, card, badge)
│ │ ├── upload/ # Drag-and-drop file uploaders with progress
│ │ └── waitlist/ # Pre-launch email capture components
│ ├── config/ # Static configuration (plans, free-models, vercel, payment-test)
│ ├── lib/ # Browser & client utilities (supabase client, crypto, origin)
│ ├── routes/ # TanStack Start file-based routing
│ │ ├── -studio/ # Studio workspace UI (Editor.tsx, preview, shortcuts)
│ │ ├── api/ # Server route endpoints (auth, uploads, generations, sites, domains, mcp, export)
│ │ ├── [.]well-known.* # OAuth 2.1 authorization-server and protected-resource discovery
│ │ ├── admin.tsx # Standalone admin dashboard
│ │ ├── authorize.ts # OAuth 2.1 consent & authorization screen
│ │ ├── connect.tsx # External chat app connector view
│ │ ├── index.tsx # Marketing landing page
│ │ ├── login.tsx # User authentication
│ │ ├── mcp.ts # Streamable HTTP Model Context Protocol endpoint
│ │ ├── onboarding.tsx # Multi-step portfolio creation wizard
│ │ ├── register.ts # RFC 7591 dynamic client registration
│ │ ├── s.$.ts # Public sandboxed portfolio serving (/s/<slug>)
│ │ ├── settings.tsx # User settings, key management, connectors & danger zone
│ │ ├── studio.tsx # Studio route shell
│ │ └── token.ts # OAuth 2.1 token grant & refresh endpoint
│ ├── server/ # Backend business logic & security infrastructure
│ │ ├── dodo/ # Dodo Payments checkout & webhook processing
│ │ ├── generation/ # Background jobs, free provider harness, queue & runner
│ │ ├── github/ # GitHub App installation token & Git Data API commit push
│ │ ├── llm/ # LLM adapters (Google Gemini, OpenAI-compatible) & guardrail prompts
│ │ ├── mcp/ # MCP JSON-RPC protocol, tool registry, and OAuth token store
│ │ ├── template/ # HTML image embedding and string escape utilities
│ │ ├── vercel/ # Vercel REST API client for domain registration & status checks
│ │ ├── auth-middleware.ts # Session validation & AuthedContext extraction
│ │ ├── checked-generation.ts # Guardrail generation + numeric fact check pipeline
│ │ ├── domains.ts # Domain validation, TXT verification formatting & tokens
│ │ ├── fact-check.ts # Fact-checking numbers, %, currency, and years
│ │ ├── finalize-html.ts # Output verification, link sanitization, mXSS defense
│ │ ├── portfolio-inputs.ts # Wizard inputs persistence
│ │ ├── portfolio-versions.ts # Portfolio version history management
│ │ ├── rate-limit.ts # Rolling-hour rate limiting
│ │ ├── resume-texts.ts # Extracted resume text storage (service-role only)
│ │ ├── sites-serve.ts # Core logic for sandboxed /s/<slug> response rendering
│ │ ├── sites.ts # Site slug validation, routing, and reservations
│ │ ├── storage-markers.ts # URL marker translation for persistent HTML
│ │ ├── users.ts # User entitlements & server-side hasPaidAccess()
│ │ └── watermark.ts # Server-side watermark injection for free tier
│ ├── styles.css # Tailwind v4 theme tokens: Graphite/Ink/Mist neutrals, Aurora accents (the logo palette)
│ ├── routeTree.gen.ts # Auto-generated TanStack router tree
│ └── start.ts # TanStack Start server entry point & CSRF origin check
├── supabase/
│ ├── functions/ # Supabase Edge Functions (purge-storage-objects)
│ └── migrations/ # Postgres migrations (0001 through 0021)
├── brand/logo/ # Logo kit (Leaf Launch, Aurora): SVG/PNG masters, web icons, guidelines, presentation
├── API.md # Canonical API contract for all endpoints
├── DESIGN.md # Design system tokens, color calibration & taste standards
└── version2memory.md # Source of truth for product architecture & decision history
API.md) — Exhaustive specification of every endpoint (method, path, authentication, payload, responses, and error codes).DESIGN.md) — Visual design contract, typography hierarchy, Aurora palette (blue #3457E8, cyan, mint) and Graphite neutrals, and layout guidelines.docs/ARCHITECTURE.md) — Detailed pipeline flow, security boundaries, database isolation, and CSP policies.userflow.md) — Step-by-step current and proposed product flows with Mermaid sequence diagrams.version2memory.md) — The primary source of truth recording all technical decisions, scope boundaries, and architectural choices.FREE_BUDGET_OPENROUTER_A..DFREE_MODEL_OPENROUTER_A..D | For Free tier | OpenRouter model IDs for fallback slots A through D. Strict Rule: Every OpenRouter model ID MUST end with :free (e.g. nvidia/nemotron-3-super:free). Enforced in code (src/config/free-models.ts): any model without the :free suffix is automatically skipped so it can never bill the OpenRouter account |
GROQ_API_KEY | For fast free builds | Groq key (free, no card). When set, Groq's models are used first. Models come from FREE_MODELS_GROQ (comma separated; default openai/gpt-oss-120b,openai/gpt-oss-20b,qwen/qwen3.8-27b). Budgets per model: FREE_BUDGET_GROQ (default 55 calls a day, about 200K tokens) |
CEREBRAS_API_KEY, FREE_MODEL_CEREBRAS | No | Optional tertiary free provider key and model ID |
GITHUB_APP_ID | For GitHub export | GitHub App numeric ID |
GITHUB_APP_SLUG | For GitHub export | GitHub App slug name (e.g. shipfolio-ai-export) |
GITHUB_APP_PRIVATE_KEY | For GitHub export | GitHub App private key PEM with newlines escaped as \n in a single line |
GITHUB_APP_CLIENT_ID | For GitHub export | GitHub App OAuth client ID (required to verify user installation ownership) |
GITHUB_APP_CLIENT_SECRET | For GitHub export | GitHub App OAuth client secret |
VERCEL_API_TOKEN | For Custom domains | Vercel REST API token with domain management permissions |
SITES_PROJECT_ID | For Custom domains | Vercel Project ID of the shipfolio-sites deployment |
VERCEL_TEAM_ID | No | Vercel Team ID if the shipfolio-sites project belongs to a team |
OAUTH_VERCEL_CLIENT_ID | For Vercel deploy | Vercel OAuth app client ID (for deploying to user's personal Vercel account) |
OAUTH_VERCEL_CLIENT_SECRET | For Vercel deploy | Vercel OAuth app client secret |
PAYMENTS_ENABLED | No | Gate for paid features (true by default). Set to false to make all features free for testing or launch promotions |
DODO_ENVIRONMENT | Yes | Dodo Payments environment (test_mode or live_mode) |
DODO_PAYMENTS_API_KEY | Yes | Dodo Payments secret API key |
DODO_PRODUCT_ID_PRO | Yes | Dodo product ID of the USD "Pro + 1 year hosting" product ($9 paid 365-day trial, then $7.99/yr, tax-inclusive) |
DODO_PRODUCT_ID_PRO_IN | Yes | Same offer as an INR product (₹499, then ₹449/yr) used for buyers in India |
DODO_PRODUCT_ID_HOSTING | Yes | Renewal-only yearly hosting product in USD ($7.99/yr, no trial), sold to Pro users whose hosting lapsed |
DODO_PRODUCT_ID_HOSTING_IN | Yes | Same in INR (₹449/yr) for buyers in India |
DODO_WEBHOOK_SECRET | Yes | Dodo webhook signing secret (whsec_...) |
PAYMENT_TEST_MODE | No | Dev-only dummy checkout bypass (PAYMENT_TEST_MODE=dummy). Automatically disabled in production |
ADMIN_USERNAME | Yes | Superuser username for the standalone /admin dashboard |
ADMIN_PASSWORD_HASH | Yes | Scrypt hash of admin password generated via node scripts/hash-admin-password.mjs |
ADMIN_SESSION_SECRET | Yes | 32-byte base64 random secret for signing admin session cookies |
VITE_LAUNCH_MODE | No | live (default) or waitlist (swaps CTAs for pre-launch email capture form) |
WAITLIST_WEBHOOK_URL | If waitlist mode | Google Apps Script Web App URL to receive waitlist email rows |
WAITLIST_WEBHOOK_SECRET | If waitlist mode | Secret shared with Google Apps Script to verify inbound requests |
/api/cron/hosting/terms, /refund, /privacy, and the pricing block on the landing page (src/components/billing/PricingSection.tsx). The "Today / You get / Next year" box (OfferSummary) appears wherever someone is about to pay.