It Started With a YouTube Video
About two months ago I was doing my usual late-night YouTube rabbit hole when I stumbled on a video from Peter Yang interviewing a creator named Alex Finn. I almost scrolled past it.
The thumbnail said something about Claude Code and a "life operating system." I clicked mostly out of curiosity. (If you want to watch it yourself: Full Tutorial: Build an AI Life Co-Pilot with Claude Code in 25 Minutes)
I watched Alex walk through how he had built a system — running entirely in his terminal — that acted like an autonomous employee. Custom slash commands. Sub-agents that browse the web, scrape competitor content, and draft reports in his specific writing voice. He had it tracking his personal goals, analyzing his mood patterns over time, generating daily industry briefings. The whole thing.
My first reaction wasn't "I want to build a product." It was simpler than that — I just wanted to copy what he was doing.
Some of his use cases felt immediately relevant to my life. The newsletter researcher that scans industry content on a schedule. The daily check-in that tracks mood and goals over time. The morning briefing that pulls together news in a specific domain. I thought: I could use all of that. I don't need to be a developer to set up my own version of this system.
So I opened my terminal and started trying.
And somewhere in that process of just trying to replicate his setup — tinkering, prompting, watching things actually get built — something shifted. I realized the gap between where I was and a shipped product was much smaller than I'd always assumed.
I've spent years being someone who has product ideas and then immediately shelves them because "I'd need a developer for that." I work with data and strategy. I can write SQL. I understand how systems work conceptually. But actually building a product, from scratch, with a real UI and an API? That always felt like it belonged to a different category of person.
Turns out the category was mostly in my head. And once I saw that, I stopped trying to replicate someone else's setup and started thinking about what I actually wanted to build for myself.
The Idea: Resume-Lens
The problem didn't come from reading a blog post or brainstorming in a notes app. It came from something I was dealing with at work.
I was hiring more analysts for my team. I'd put out a job posting, and the applications started flooding in — dozens of them, all arriving by email. Resumes as attachments. Varying formats, varying lengths, wildly varying quality. No structure. No easy way to filter.
The problem wasn't finding candidates. The problem was that I was spending an embarrassing amount of time just trying to figure out which resumes were worth reading closely. I'd open an attachment, skim it, try to hold the job description in my head at the same time, make a snap call, and move on. Multiply that by 40 applicants and it starts to feel like a full-time job on top of your actual job.
I kept thinking: someone should be doing a first pass on these before they get to me. Not rejecting people — just flagging how well each resume actually matches what I said I was looking for. Keywords, experience level, skills. Basic stuff that takes a human 5 minutes per resume and an AI about 5 seconds.
So I built it. Not for job seekers — for myself, as a hiring manager trying to not drown in email.
Resume-Lens is a platform for hiring managers — not job seekers:
- You create a job posting, write the technical requirements, and define your scoring criteria — things like "minimum 3 years experience," "Bachelor's degree or higher," "related experience in data analysis"
- You upload batches of PDF resumes from candidates — drag, drop, done
- The tool extracts structured data from each PDF (name, education, total experience, last company, and more), then scores every candidate against your criteria and requirements
- You get a ranked, filterable candidate table — sorted by score, searchable by name or education — so you can decide who's worth interviewing without opening a single attachment manually
It's not a "make your resume better" tool. It's a "I have 40 resumes in my inbox, show me the top 10 worth interviewing" tool. The filtering layer I needed but couldn't find anywhere.
What is Vibecoding, Actually?
Before I get into the how, let me explain vibecoding the way I understand it — because when I first heard the term I thought it was some gimmick.
Vibecoding is coding by intent instead of by syntax. You describe what you want to happen in plain language, and you let an AI write the actual code. You're not passively watching it generate random stuff — you're directing it. You review what it produces. You push back when it's wrong. You steer it toward the right solution.
The "vibe" part is real, though. There's something about the flow of it that feels different from traditional development. You're not context-switching between documentation, Stack Overflow, and your editor. You're having a conversation with something that already knows all of that, and your job is to be clear about what you actually want.
For non-developers like me, the mental shift is big. You stop thinking "I need to learn how to code this" and start thinking "I need to clearly describe what this should do." Those are very different problems. The second one is one I already know how to solve.
Setting Up Claude Code
I'll be honest — even setting up Claude Code felt intimidating at first. But it's really just a CLI tool you install once.
npm install -g @anthropic-ai/claude-codeYou'll need Node.js installed and an Anthropic API key. Once you have both, you navigate to wherever you want your project to live and run:
claudeThat's it. You're now in a conversation with something that has full access to your file system, can write code, run commands, read errors, and fix itself. It feels a little surreal the first time.
My first message to it was embarrassingly non-technical:
"I want to build a hiring platform called Resume-Lens for internal use. Hiring managers upload PDF resumes, the app extracts structured candidate data using an AI API, stores everything in a Supabase database, and scores candidates against job-specific criteria. Build it as a Next.js app with a Python backend for the AI and PDF parsing, with a dashboard to manage multiple job postings."
And then I watched it go.
Step by Step: Building Resume-Lens
Step 1 — Scaffolding the Project
Claude Code started by initializing a Next.js project with TypeScript and Tailwind CSS. It set up the folder structure, configured the app router, and got the dev server running. This part took maybe two minutes and zero input from me beyond that first prompt.
What I noticed immediately: it made reasonable decisions without asking me for every little thing. It picked sensible defaults. I didn't have to know that app/ router exists or what tailwind.config.ts is supposed to look like. It just set those up.
resume-lens/
├── api/ # Python serverless functions (Vercel)
│ ├── auth.py # Password gate
│ ├── parse.py # PDF extraction + AI parsing
│ ├── candidates.py # Candidate CRUD
│ ├── jobs.py # Job CRUD
│ ├── score.py # Hybrid scoring engine
│ └── share.py # Shareable link generation
├── app/ # Next.js App Router
│ ├── page.tsx # Login
│ ├── layout.tsx
│ ├── dashboard/ # Job list + stats
│ ├── jobs/
│ │ ├── new/ # Create job form
│ │ ├── [id]/ # Job detail + candidate table
│ │ └── [id]/upload/ # PDF upload
│ ├── candidates/
│ │ ├── page.tsx # All candidates (cross-job)
│ │ └── [id]/ # Candidate detail + scores
│ └── share/[token]/ # Public read-only view
├── components/
│ ├── shared/
│ ├── jobs/ # JobForm, CriteriaBuilder, ShareButton
│ ├── candidates/ # CandidateTable, ScoreBadge, Filters
│ └── upload/ # PdfDropzone, UploadProgress
└── lib/
├── api.ts # API client wrappers
├── types.ts
└── auth.ts # Session storage auth
Step 2 — Building the Job Creation and Upload UI
The app needed two main interfaces: a form for creating a job posting (with a criteria builder), and an upload page for dropping in PDF resumes.
The criteria builder was the interesting part. I wanted hiring managers to be able to define their own scoring rules without writing code — things like "total experience must be at least 3 years" or "education must be at least Bachelor's." Each rule gets a weight, and the weights have to sum to 100%.
I gave Claude Code a rough sketch of what I wanted:
"Build a criteria builder UI where users can add rows. Each row has a field dropdown (experience, education, age, etc.), an operator dropdown (
>=,<=,=), a value input, and a weight input. Add validation that the weights must sum to 100% before the form can submit."
It built the component. The validation logic — checking the sum in real-time and blocking submission — it figured out on its own. I just told it what the constraint was.
For the upload page, I described a PDF dropzone with drag-and-drop support, a list showing upload progress per file, and a preview of the extracted data once parsing was done. The iteration there was mostly visual — spacing, status indicators, making the parsed preview feel readable rather than raw JSON on screen.
Step 3 — Connecting the AI: Two Separate Jobs
This is where the architecture got interesting. The AI does two distinct things in Resume-Lens, and they needed two separate Python functions.
First: parsing. When a PDF is uploaded, parse.py extracts the raw text using PyPDF2, then sends it to the LLM to pull out structured fields — name, email, education, total years of experience, related experience, last company. The model used is Qwen3 VL 235B via OpenRouter, running at low temperature so the extraction is consistent.
The extraction prompt Claude Code wrote:
system_prompt = """Extract candidate information from the resume text and return JSON only.
Fields: name (Proper Case), email, phone, gender, age, last_education,
last_company, total_experience (decimal years), related_experience (decimal years).
Handle both Indonesian and English resumes.
For age: calculate from birth date if available, or estimate from education start year
(assume Bachelor's starts at 18). Return null only if no date information exists.
Return raw JSON only, no markdown."""The bilingual handling was something I asked for specifically — most of our applicants write resumes in Indonesian, and I needed the parser to handle both.
Second: scoring. After candidates are saved, score.py runs a hybrid evaluation: 60% from the criteria the hiring manager defined (evaluated with hard logic — experience >= 3, education rank >= Bachelor, etc.), and 40% from an LLM that reads the resume against the job requirements and returns a 0-100 relevance score.
# Simplified scoring logic
criteria_score = evaluate_criteria(candidate, job.criteria) # 0-100
ai_score = call_llm_relevance(candidate.raw_text, job.requirements) # 0-100
total = (criteria_score * 0.6) + (ai_score * 0.4)The 60/40 split was my decision, not Claude Code's. I wanted the structured rules to dominate — if I say I need someone with at least 3 years of experience, a great essay score shouldn't override that. The AI relevance portion is a tiebreaker more than anything else.
I didn't write any of that Python. I described the logic I wanted, Claude Code implemented it, and I reviewed whether the math and the intent matched. That's the whole workflow.
Step 4 — Displaying the Results
The candidate table was the most important UI piece — it's the thing I'd actually be staring at every day. I wanted it to be sortable by any column, filterable by field (score, education level, years of experience), and searchable by name or company.
I asked for:
- A score badge per candidate, color-coded (red for low, green for high)
- Clickable column headers for sorting
- A filter builder where I can stack conditions ("score >= 70 AND education = Bachelor")
- A candidate detail page showing the per-criteria score breakdown alongside the raw extracted resume text
Claude Code built the sortable table and filter UI without much trouble. The part that took more iteration was the score breakdown on the candidate detail page — showing how much of the score came from each criterion versus the AI relevance portion, in a way that actually made sense visually.
There's also a shareable link feature — I can generate a read-only URL for a job and send it to a colleague, and they can see the ranked candidate list without needing to log in. That one I asked for as an afterthought and it was built in about 15 minutes.
Step 5 — The Parts That Broke (And How We Fixed Them)
I'm not going to pretend it was frictionless. A few things broke.
Problem 1: The LLM was returning JSON with markdown code fences around it — the response would come back as ```json { ... } ``` instead of raw JSON. The parser was crashing trying to feed that directly into json.loads().
"The parse endpoint is crashing on some resumes. The LLM is wrapping the JSON in markdown code fences and we're not stripping them before parsing."
Claude Code added a cleanup step that strips fences, handles truncated responses (where the LLM cuts off mid-JSON), and as a last resort returns a null object instead of crashing. That logic is still in the code today — it catches edge cases I didn't anticipate.
Problem 2: Bulk scoring was slow and there was no feedback while it was running. If I kicked off scoring for 30 candidates, the page just froze.
"Scoring 30 candidates with no progress feedback feels broken. Can you make it score them one by one and update the UI after each one completes — show a progress bar or status per candidate?"
It reworked the scoring flow to process candidates sequentially and update state after each one, so the table fills in progressively. Much better.
Problem 3: The OpenRouter API was occasionally returning rate limit errors (429) during bulk scoring runs.
This one Claude Code caught before I even noticed — when I described the bulk scoring architecture, it preemptively added exponential backoff retry logic to the scoring function. Three attempts, waiting 3 seconds then 6 seconds between retries. I asked where that came from and it explained that rate limit handling is standard practice for API calls in a loop. Good instinct.
What Resume-Lens Actually Does Now
Here's the current state of the product.
You log in with a password (no user accounts — it's just for me and anyone I share access with). You land on a dashboard showing all your active job postings and total candidate counts.
You create a job by writing the title, a description, the technical requirements (used for AI evaluation), and your scoring criteria. The criteria builder lets you add rules like:
- Total experience >= 3 years (weight: 40%)
- Education >= Bachelor's (weight: 35%)
- Related experience >= 1 year (weight: 25%)
Those weights must sum to 100%. That constraint enforces you to actually think about what matters most for the role.
Then you go to the upload page, drop in PDF resumes — one at a time or a whole batch — and the app does two things per file: extracts structured candidate data (name, education, years of experience, last company, etc.), then saves it to the database. The parsing handles both Indonesian and English resumes, which matters for my team.
Once candidates are uploaded, you run scoring. You can score all at once or select specific candidates. Each gets a score from 0 to 100:
- 60% from your criteria — hard logic, evaluated exactly as you defined it. Experience >= 3 years: pass or partial credit. Education below your minimum: penalized proportionally.
- 40% from AI relevance — the model reads the resume against your requirements and rates how well the candidate actually fits the role.
The result is a ranked candidate table. You can sort by score, filter by education level or experience, and search by name or company. Click any candidate to see the full breakdown: how they scored on each criterion, what the AI said about their relevance, and the raw text extracted from their resume.
There's also a share feature — generate a read-only link for any job and send it to someone who doesn't have login access. They see the same ranked table, no editing.
The whole flow — from uploading a batch of resumes to having a ranked shortlist — takes maybe 5 minutes depending on how many candidates you're processing.
What Vibecoding Actually Feels Like
I want to be honest about the texture of this experience, because I think it's different from what people expect.
It's not magic. It's fast iteration. The product didn't appear from a single prompt. It came from maybe 50-60 exchanges with Claude Code over three days. Some of those were big ("build the results panel") and some were tiny ("make the score circle slightly thicker"). The speed comes from collapsing the feedback loop, not from eliminating the work of thinking.
You still need to have taste. Claude Code will build what you describe. If your vision is fuzzy, the output will be fuzzy. The clearer I got about what I actually wanted — not just "make it look good" but "I want a card-based layout with subtle shadows and muted colors, professional not playful" — the better the results.
You will see code you don't fully understand. This happened to me constantly. I'd look at what Claude Code wrote and think "I have no idea how that works, but it seems to work." That's uncomfortable if you're a perfectionist. I got over it by reminding myself: the goal is a working product, not a CS degree. I still reviewed for logic — does this do what I asked? Does it expose any obvious security issues? — without needing to understand every line.
The moments where it surprised me were genuinely fun. There were a few times where Claude Code built something slightly different from what I asked for, and it was actually better. A loading skeleton animation I didn't ask for. A debounce on the text inputs I didn't think about. It felt collaborative in a way I didn't expect.
Lessons From Someone Who Isn't a Developer
If you're reading this and you're like me — you have product ideas, you understand technology conceptually, but you've never shipped something — here's what I actually learned:
Start embarrassingly small. My first prompt was "build a web app called Resume-Lens." That's it. Don't try to spec out every edge case before you've seen anything on screen. Build the skeleton first. The details come from reacting to something real.
Treat broken things as prompts, not failures. When something doesn't work, that's just the next message you send to Claude Code. Describe what's wrong. It almost always knows what to do with that.
Descope aggressively. I had plans for user accounts, saved analyses, a history view, email exports. None of that is in the MVP. I shipped something useful without any of it. You can always add features later. You can't ship something you haven't finished.
Read what gets generated. You don't need to understand it all. But at minimum, understand what it's doing and why. If Claude Code writes an API key into a frontend component, you need to catch that. The judgment layer is still yours.
The bottleneck moved. It's not "can I build this?" anymore. It's "is this the right thing to build?" That's a much better problem to have.
What's Next for Resume-Lens
The immediate thing I want to add is email ingestion — right now I still have to manually download attachments and upload them. The dream is a forwarding address: send candidate emails there and they parse and land automatically.
I also want to improve the scoring calibration. The 60/40 split between criteria and AI relevance works, but I want to be able to tune it per job. Some roles are highly criteria-driven (you need exactly X years), others are more holistic. A slider to adjust the weight per posting would make the tool more flexible.
Eventually I want to think about whether there's a business here. But right now, honestly, the point was to prove to myself that I could build something real.
I can.
And the thing that started that proof was a YouTube video about a guy named Alex Finn who built his entire life operating system in his terminal and made it look like the most obvious thing in the world.
If you're on the fence about trying this — just try it. The worst case is you spend a weekend and build something that doesn't work. The best case is you realize that the category of "things I could build" is much larger than you thought.
For me, it was both. And I wouldn't trade either part.
Resume-Lens is available at resume-lens. The code is on GitHub if you want to see what vibecoded Next.js actually looks like.