Clone a repo, drop in your resume PDF, type one command, and Claude Code generates a complete SEO-ready Next.js portfolio. Here's how PortfoPilot turns a fuzzy task into a reliable multi-agent pipeline — staged subagents, a typed JSON contract, skills, and deterministic safety hooks.
Every developer has the same chore sitting on their to-do list: build a personal portfolio site. And every developer keeps pushing it down the list, because the work is annoying in a very specific way; it isn't hard, it's just tedious. You copy bullet points out of your resume, fight with a CSS grid, and wire up an SEO meta tag you'll forget to update, and three weekends later you have a half-finished site you're embarrassed to deploy.
I built PortfoPilot to delete that chore entirely. You clone a repo, drop in resume.pdf, type one command, and Claude Code generates a complete, SEO-ready Next.js portfolio: pages, components, theme, structured data, sitemap, the lot. No manual content entry. Your resume is the source of truth.
But the interesting part isn't the output. It's how it's built. PortfoPilot is really a case study in agentic development: how you turn a fuzzy creative task into a deterministic pipeline that an LLM can execute reliably. If you've been poking at Claude Code, MCP, and subagents and wondering what a real multi-agent workflow looks like beyond the demos, this is the part worth your attention.
git clone https://github.com/pravinharchandani/portfopilot.git my-portfolio
cd my-portfolio
# drop your LinkedIn-exported resume in as resume.pdf
claude
> /build-portfolio
npm run dev # http://localhost:3000
That's the whole happy path. Export your resume from LinkedIn (Resources → Save to PDF), rename it to resume.pdf, run /build-portfolio, and you get a working site running locally. Push to GitHub, import into Vercel, set one environment variable, and you're live on the free tier.
The lazy way to ship this would be a Next.js starter template with placeholder text and a README that says "now go edit everything yourself." That's not a generator — that's homework. The whole value is in removing the manual content work, and that's exactly the part a template can't do, because every resume is different. LinkedIn exports vary by region, language, and how much effort the person put into their profile. Sections go missing. Project descriptions are thin or absent. Dates are formatted six different ways.
So the real engineering challenge is: take an unstructured, inconsistent PDF and reliably turn it into a structured, validated website without hallucinating credentials someone doesn't have. That's an agent's job, not a template's. And the way you make an agent reliable at it is the same way you make any system reliable — you decompose it into stages with clear contracts between them.
Running /build-portfolio kicks off an orchestrator that hands work to specialist subagents in sequence. Each stage has a single responsibility and a single output, so when something breaks you know exactly which agent to debug instead of staring at a 2,000-line generation blob.
resume.pdf existence, installs dependencies, and scaffolds the Next.js App Router project if it isn't there yet.resume-parser): reads the PDF and writes data/resume.json - structured, validated, and non-fabricated content.content-mapper): Decides what content lands on which page, picks a theme based on your profile, and writes data/site-plan.json.ui-generator): writes every route, component, theme token, and SEO artifact.The design decision I care most about here: the JSON is the contract. After Stage 1, the PDF is never touched again. Every downstream agent reads data/resume.json, never the PDF. That single rule is what makes the system maintainable — the messy, probabilistic extraction is quarantined to one stage, and everything after it operates on clean, typed data. It's the same instinct as putting a validation layer at the edge of an API so the rest of your code can trust its inputs.
The contract between stages is a TypeScript interface, and the most important thing about it is that almost every field is optional:
interface ResumeData {
name: string;
headline?: string;
summary?: string;
experience?: { company: string; title: string; bullets?: string[] }[];
skills?: { category: string; items: string[] }[];
projects?: { slug: string; title: string; description: string }[];
// ...education, certifications, links
}
Those question marks are doing heavy lifting. They encode a hard rule baked into CLAUDE.md: missing data is omitted, never invented. If your resume has no projects section, which is common, LinkedIn exports rarely capture project detail well, the generator surfaces a TODO and leaves projects: [] rather than fabricating case studies you never worked on. For a tool that represents real people to real recruiters, that's not a nice-to-have. A portfolio that lists a job you didn't have is worse than no portfolio.
If you want to understand modern Claude Code setups, the .claude/ directory in this repo is a compact, real-world reference. It's not a toy — every piece maps to a concept you'll reuse in your own projects:
commands/build-portfolio.md) as the orchestrator, the single entry point a user types.agents/), each with its own focused system prompt: parser, mapper, generator, plus optional design and SEO reviewers.skills/) carrying domain knowledge that would otherwise bloat every prompt, LinkedIn PDF quirks, component patterns, the design-token system, and JSON-LD/sitemap patterns.hooks/post-edit-validate.sh) that runs after edits to block writes to sensitive files and enforce lint/typecheck.That hook is quietly the most important safety feature. It actively blocks the agent from ever writing your resume.pdf or .env files into a committable location and warns if your .gitignore is missing the right entries. The agent cannot leak your personal data into a public repo, because the tooling around it refuses the write. This is the right pattern for agentic systems generally: don't trust the model to remember a rule, enforce the rule deterministically in a hook, where it can't be reasoned away.
Here's a detail I'm proud of. The generator inspects your profile and, if it detects a technical background, "Engineer," "Developer," or "Architect," or programming-language and framework skills — it applies a VS Code-inspired theme: Dark+ palette, monospace headings, a terminal-style hero prompt, sidebar nav styled like the file explorer, skill categories as editor tabs. For a developer audience it reads as instantly familiar. Non-technical profiles get a clean, profession-appropriate theme instead.
The implementation detail that makes this sane is that every theme is just CSS custom properties plus Tailwind tokens. The component code never hardcodes a color. So switching themes later, or letting the agent re-theme on request, is a token swap in globals.css and tailwind.config.ts, not a rewrite of fifty components. This is the same separation-of-concerns discipline you'd want in any design system; the agent just enforces it from the first line of generated code.
The generated site treats SEO as a build requirement rather than a later cleanup task. Every route exports proper metadata (or generateMetadata for the dynamic /works/[slug] pages) with OpenGraph and Twitter cards and a canonical URL. There's a shared <JsonLd> component emitting structured data per page type — Person schema on home and about, ItemList on the works listing, and CreativeWork on project detail pages. app/sitemap.ts and app/robots.ts use the Next.js Metadata API to auto-generate sitemap.xml and robots.txt, with the dynamic project routes pulled straight from your JSON.
All of it keys off a single NEXT_PUBLIC_SITE_URL environment variable, so canonical URLs, sitemap entries, and robots all stay consistent and there's exactly one place to set your domain. For a personal site whose entire job is to rank when someone Googles your name, having Google-readable structured data on day one is the difference between showing up and not.
Once the site exists, it data/resume.json becomes your file. Fix a typo, reword a bullet, or add a project by editing the JSON directly — the UI reads from it live in dev. You only go back to the PDF if you want a fresh extraction from an updated resume. The PDF was scaffolding; the JSON is the building.
PortfoPilot scratched a personal itch, but the architecture is the real lesson. The pattern — decompose a fuzzy task into staged subagents, define a typed JSON contract between them, push domain knowledge into skills, and enforce the non-negotiable rules in deterministic hooks — generalizes far beyond portfolios. Swap the resume parser for an invoice parser, or the UI generator for a report generator, and you have the same reliable spine.
If you've been treating Claude Code as a fancier autocomplete, this is the shift worth making: stop prompting it task by task, and start designing the pipeline. Clone the repo, read the CLAUDE.md spec and the .claude/ folder, and you'll have a working template for how to build agentic systems that actually hold up. And you'll get a portfolio site out of it for free.
github.com/pravinharchandani/portfopilot — clone it, drop your PDF, ship your site.