logo
Alexandre Marques
Back to blog

Building My CV Like a Product: Designing and Shipping with AI, Side by Side

I built my CV like a product: designed in Pencil, exported and published by custom Claude Code skills, with a real renderer bug debugged along the way.

Alexandre Marques
Alexandre Marques
·8 min read
Building My CV Like a Product: Designing and Shipping with AI, Side by Side

I redesigned my CV recently. Not in Figma, not in Canva, not in a Word template — in a .pen design file, versioned in git, exported to a PDF, and pushed to my own assets CDN domain by a couple of scripts I don't even have to run by hand. You can think of the whole thing as a tiny product, built side by side with AI: design, code, and ops in one loop. Sounds nerdy? Probably — but don't worry, I'm a dev who likes to stay in control, even when AI is doing the heavy lifting. One bit of context up front: my whole setup here is the Claude Code extension running inside Cursor — worth flagging now, because a bug I hit later might be specific to that particular combo rather than something universal. So let's dive in!

Ah, wait, just a sec — what's a .pen file? It's the design file for Pencil, a vector editor, so the CV is a real design, not a document template, living right there on my machine. You might ask why I didn't just reach for Claude Design or one of the other trendy AI-powered design tools. The answer is simple: I didn't want to leave my editor to hop into yet another web app. I wanted to be in the loop — moving a box here, fixing a margin there by hand when needed, alongside my other coding projects, on my own machine — automating the boring parts from a single place.

Here's the project structure:

cv/
├── cv.pen                          # source design (Pencil) — the single source of truth
├── cv.pdf                          # exported vector output
├── resources/                      
│   ├── avatar.png                  # profile photo 
│   └── personal_brand_logo.png     # monogram
├── .claude/
│   └── skills/
│       ├── cv-pdf/                 # /cv-pdf  — export vector PDF + stamp links (more on this later)
│       │   ├── SKILL.md            # the skill definition
│       │   └── scripts/build_cv_pdf.py
│       └── cv-upload/              # /cv-upload — push to R2 + purge edge cache (more on this later)
│           └── SKILL.md            # the skill definition
├── CLAUDE.md                       # design system + working rules (the AI reads this every session)
├── .env                            # secrets, gitignored
└── README.md                       # the project README

Designing side by side

Pencil ships an MCP (Model Context Protocol) server out of the box with its IDE extensions for Cursor and VS Code. So the setup is genuinely just: install the extension, open the .pen file, log in, and start designing. With the file open in the editor, Claude Code detects it and can drive the design directly — read the current layout, tweak an element, take a screenshot, look, adjust. That screenshot-in-the-loop matters more than it sounds: the model isn't designing blind, it's reacting to the rendered result the same way I would.

What made this not slop:

Treating the AI as a design pair — with guardrails — beats treating it as a one-shot generator. The guardrails live in a CLAUDE.md the model reads every session, which is really just the project's design doc doing double duty. If the design system outgrows that single file, there's now a tidy convention for exactly this: DESIGN.md — a markdown file that hands a design system to coding agents, pairing machine-readable tokens with the prose rationale behind them. You'd drop the tokens, fonts, and layout rules into a dedicated DESIGN.md and pull it into CLAUDE.md, so the design system lives in one focused doc the AI reads every time.

The boring parts, automated

Two tasks happen on every update: export the PDF and publish it. Both have sharp edges, so both became skills — small, named, reusable workflows Claude Code can invoke (/cv-pdf, /cv-upload).

A skill is more than a script. It's encoded institutional memory: the steps plus the gotchas you only learn by getting burned. For example, /cv-upload doesn't just push to my Cloudflare R2 bucket — it then purges the Cloudflare edge cache, because the domain caches the PDF for a few hours and without the purge the old file serves for the rest of the afternoon while you swear at your browser. That lesson is now baked into the skill so I never have to relearn it.

This is the part I'd most encourage other people to copy: when you find yourself explaining a fiddly multi-step process to an AI for the second time, turn it into a skill. You're building yourself a shortcut you'll reach for again and again!

The bug hunt

Now for the fun part. When I went to export the PDF through Pencil's MCP, it threw this at me:

MCP error -32603: failed to execute tool call.
you are probably referencing the wrong .pen file

Rude! Nothing was wrong with the file: exporting that same file to PNG, through that same MCP server, worked perfectly. (Remember that setup caveat from the top — this might be specific to my exact combo, so don't read it as "Pencil is broken." The fun part is the chase, not the bug.) So I did what I'd do with any incident at work: stop guessing, start gathering evidence — with Claude Code doing most of the legwork.

Evidence #1 — the error is misleading. It turns out the MCP "server" is just a little program that passes my requests along to the real renderer inside the editor. And that "wrong .pen file" message? A generic catch-all it tacks onto any failure — friendly-looking, completely unhelpful.

Evidence #2 — read the source, then bisect. Next I cracked open the rendering code (it ships in a readable form) and noticed the PDF path does something the PNG path doesn't: to keep the exported text selectable, it tucks an invisible text layer into the file. So I had Claude Code export the page piece by piece — and every piece was happy on its own. Only when the whole main column went out as a single PDF page did it freeze, every single time. A hang that depends on the exact content, hiding inside a renderer I can't peek into.

So the verdict: this almost certainly wasn't my file or my config — just a misleading error and a bug I couldn't reach from where I sat. And I only got there by reading the source and measuring, instead of taking that error message at its word.

The fix

Pencil also ships a CLI built on the same engine but with a headless renderer. On a hunch that headless might dodge the in-app hang, I tried it:

npx -y @pencil.dev/cli@latest --in cv.pen --export /tmp/cv.pdf --export-type pdf

Six seconds. A valid, vector PDF with selectable text. The headless renderer sidesteps whatever the in-editor one trips over.

It had one gap — like the native export, it embeds no clickable link annotations. So I reworked /cv-pdf into a two-step pipeline: export the vector PDF with the CLI, then stamp the links back on — contact details, the highlight links, and the footer URL — with PyMuPDF, scaling the page to true A4 along the way. The result is strictly better than where I started: selectable, searchable text and working links, crisp at any zoom — instead of a flat image.

The bug became a feature upgrade. That happens more than you'd expect when you refuse to stop at the first workaround!

A quick aside on MCP vs CLI: for commands a model already knows by heart — git, npx, curl, the stuff that fills its training data — a plain CLI is often more token-efficient than an MCP server. The model just writes the command; no one has to teach it the syntax. MCP can't lean on that: its tools are custom, so it loads their full schema into context up front — often tens of thousands of tokens before you ask anything (lazy tool-discovery is starting to claw that back). The catch: a CLI needs a real place to run — my machine, or a sandbox the agent controls — with the runtime, deps, and auth actually set up.

What I'd take away

Thanks for reading! If this made you want to build your own tiny product, I'd love to hear what you build. 🚀