Skip to content

Category: The Art of Vibe Coding

• 12 min read • The Art of Vibe Coding

GPT-5.6 Sol Is Way Better in Claude Code (Here’s How to Set It Up)

GPT-5.6 Sol Is Way Better in Claude Code (Here's How to Set It Up)

I was halfway through Theo’s video when I opened a new terminal.

Couldn’t help it — I needed to see this for myself.

He was showing a setup where GPT 5.6 Sol runs through Claude Code’s interface using a local proxy. Same model you’d get in Codex, but wrapped in Claude Code’s system instructions, tools, and workflow scaffolding. Tibo posted about the same setup on X around the same time, and the results he shared looked impressive.

So I set it up.

Ran the same prompts through both environments. Compared the output side by side.

Here’s the thing.

The same model, at the same effort level, produces dramatically different results depending on where you run it. And the gap was wide enough that I wanted to document exactly what I saw — and then walk you through the full setup so you can try it yourself.

.

.

.

The Comparison: Same Model, Different Results

I gave the same three SaaS landing page prompts to GPT-5.6 Sol in Claude Code (via a local proxy called CLIProxyAPI) and directly in Codex. Same model. Same effort level (xhigh). Different surroundings.

The three prompts were for fictional products:

ProductDescription
DevlogA project board that lives in your codebase
FlowPilotA team coordination workspace
ReviewFlowA client feedback and review tool

Each prompt was a single sentence describing the product. Here’s what the prompt looked like in Claude Code and in Codex:

The same SaaS landing page prompt running in Claude Code (left) and Codex (right), both using GPT-5.6 Sol at xhigh effort

Identical prompt, identical model, identical reasoning effort. Let’s look at what came out.


Devlog

Devlog landing page comparison — Claude Code version (left) with rich multi-section layout versus Codex version (right) with fewer sections

The Claude Code version feels like a complete marketing site — multiple distinct sections, strong visual variety, and the kind of detail you’d expect from a finished product page. The Codex version is clean and professional, but reads more like a polished template with roughly half the depth.


FlowPilot

FlowPilot landing page comparison — Claude Code version (left) with richer component variety versus Codex version (right) with simpler patterns

The Claude Code version has richer component variety and more interactive elements throughout. The Codex version is visually cohesive and well-structured, but leans on simpler, more repetitive patterns.


ReviewFlow

ReviewFlow landing page comparison — Claude Code version (left) with pricing tiers, product demos, and FAQ versus Codex version (right) with fewer content sections

The Claude Code version goes deeper — more content-rich sections, more complex components like pricing tables and product demos, and the kind of page structure you’d see on a real SaaS site. The Codex version is polished and distinctive, but covers less ground overall.


What the Comparison Reveals

I opened the first Claude Code output next to the Codex version and actually said “wait, really?” out loud.

I expected a difference.

I didn’t expect it to be this obvious.

Across all three tests, the pattern held. Sol in Claude Code produced pages with more sections, more component variety, and more of the elements you’d expect on a real SaaS marketing site — pricing tables, FAQ accordions, testimonials with specific metrics, product demo sections.

Sol in Codex produced clean, professional pages every time.

The design quality was solid.

But the output was consistently simpler: fewer sections, fewer interactive components, less of the detail work that separates a landing page from a finished marketing site.

Theo called this out in his video.

Claude Code’s system instructions — its built-in knowledge of how to structure projects, use design patterns, and scaffold complete outputs — act as an invisible co-pilot that amplifies whatever model is behind it.

.

.

.

Why Sol Performs Better Inside Claude Code

Claude Code provides rich system instructions that shape how the model approaches every task.

When you ask for a landing page, those instructions guide the model toward common page structures, component patterns, and file organization conventions. The model receives a substantial context before your prompt even arrives.

Codex is a more minimal environment. It gives Sol direct access to tools and a sandbox, but less guidance on how to use them. The model has to infer structure, conventions, and completeness standards from the prompt alone.

Stay with me — because this is the part that reframes the whole comparison.

Think of it like a skilled carpenter and two different workshops.

The carpenter’s talent is the same in both rooms. But in the workshop with the jigs, the templates, and the well-organized workbench, every cut lands cleaner and every joint sits tighter. (If you’ve ever tried assembling IKEA furniture with the right Allen wrench versus a butter knife, you already know this feeling at a smaller scale.)

That’s the dynamic at play here.

Sol’s raw capability is impressive in both environments. Inside Claude Code, that capability gets channeled through a set of conventions and structural expectations that push the output toward completeness and consistency.

The takeaway: environment matters as much as raw model capability. And if you have access to Sol through your Codex allowance, you can put it inside the better environment right now.

.

.

.

The Setup Guide: Running GPT-5.6 Sol in Claude Code

Here’s the full walkthrough.

By the end of this section, you’ll have Sol running inside Claude Code with a dedicated launcher that keeps the proxy configuration separate from your normal Claude setup.

Let me show you what you need before we start.

What You Need

Before you start, make sure you have:

  • macOS or Linux — Windows users can follow the CLIProxyAPI Windows installation guide and the Claude Code Windows/WSL setup docs
  • Claude Code installed — if you haven’t yet, run curl -fsSL https://claude.ai/install.sh | bash
  • An OpenAI account with Codex access — Plus, Pro, Business, or Enterprise plans get Sol; Free and Go accounts receive Terra
  • CLIProxyAPI — a local proxy that translates between Claude Code’s API format and OpenAI’s Codex OAuth

The flow looks like this:

Architecture flow: Claude Code sends requests to CLIProxyAPI on localhost, which authenticates via OpenAI Codex OAuth and routes to GPT-5.6 Sol

Your prompts go through Claude Code’s interface, hit the local proxy, get translated into the Codex format, and reach Sol. Responses come back through the same chain. From your perspective, you’re using Claude Code exactly as you normally would — the model behind it is just different.


Step 1: Install CLIProxyAPI

macOS:

brew install cliproxyapi

Linux:

Download the installer, inspect it, and run it:

curl -fsSLo /tmp/cliproxyapi-installer \  https://raw.githubusercontent.com/router-for-me/cliproxyapi-installer/refs/heads/master/cliproxyapi-installer
Terminal showing CLIProxyAPI v7.2.88 installing on Linux — downloading the binary, extracting it, setting up configuration, generating API keys, and creating a systemd service

The installer places everything under your home directory, generates a default configuration, and creates a systemd service definition you can enable later. Don’t start the service yet — running it manually first makes configuration errors easier to spot.


Step 2: Configure for Security

The default configuration works, but a few changes make it safer for a proxy that handles OAuth credentials.

Here’s what the secure configuration does:

  • Binds to 127.0.0.1 only — prevents other devices on your network from reaching the proxy
  • Generates a random local API key — protects the proxy endpoints
  • Disables remote management and the web control panel — reduces the attack surface
  • Stores OAuth credentials in a dedicated directory under your home folder

The configuration is a YAML file. On macOS it lives at the Homebrew prefix; on Linux it’s in the CLIProxyAPI install directory.

macOS:

PROXY_KEY="sk-local-$(openssl rand -hex 32)"printf '%s\n' "$PROXY_KEY" > "$HOME/.config/claudex/proxy-key"

Linux:

mkdir -p "$HOME/.config/claudex"PROXY_KEY="sk-local-$(openssl rand -hex 32)"printf '%s\n' "$PROXY_KEY" > "$HOME/.config/claudex/proxy-key"

The key settings in your config file:

host: "127.0.0.1"port: 8317api-keys:  - "sk-local-your-generated-key-here"remote-management:  allow-remote: false  disable-control-panel: true

The full configuration script (with backup, permissions, and cleanup) is in the linked reference guide at the bottom of this post.

The API Key Gotcha: I spent a good twenty minutes staring at connection errors before I realized the default placeholder keys were still sitting in the config. The proxy was running, accepting connections, and rejecting every request. Classic config issue. The installer seeds the configuration with placeholder entries, and CLIProxyAPI deliberately blocks its proxy endpoints until all of them are removed. Removing the sample entries and leaving only the real generated key fixed it immediately.


Step 3: Connect Your OpenAI Account

CLIProxyAPI authenticates with OpenAI through a Codex OAuth flow. Run the login command, and your browser will open so you can sign in with the OpenAI account whose Codex allowance you want to use.

macOS:

cliproxyapi \  --config "$(brew --prefix)/etc/cliproxyapi.conf" \  --codex-login

Linux:

"$HOME/cliproxyapi/cli-proxy-api" \  --config "$HOME/cliproxyapi/config.yaml" \  --codex-login

If you’re on a headless or remote machine (like a Raspberry Pi), add --no-browser and use an SSH tunnel to forward the callback port. Running OAuth on a headless Raspberry Pi meant setting up an SSH tunnel just to complete the login. One of those detours that makes you question your choices for about ten minutes — and then it works and you forget you were ever annoyed.

"$HOME/cliproxyapi/cli-proxy-api" \  --config "$HOME/cliproxyapi/config.yaml" \  --codex-login \  --no-browser

When the OAuth flow completes, you’ll see this screen:

OpenAI authentication successful screen showing a green checkmark and the message 'You have successfully authenticated with Codex'

Step 4: Start CLIProxyAPI

Run the server manually first to verify everything is wired up correctly.

macOS:

cliproxyapi \  --config "$(brew --prefix)/etc/cliproxyapi.conf"

Linux:

"$HOME/cliproxyapi/cli-proxy-api" \  --config "$HOME/cliproxyapi/config.yaml"

You should see the server start up, refresh its model list from OpenAI, and begin listening on your configured address:

CLIProxyAPI server running — version 7.2.88, listening on 127.0.0.1:8317, with Codex client model refresh completed and 1 auth entry loaded

Leave that terminal open. The remaining steps happen in a second terminal.

Once you’ve confirmed it works, enable it as a background service so it starts automatically:

macOS:

brew services start cliproxyapi

Linux:

systemctl --user enable --now cliproxyapi.service

Step 5: The Launcher

Here’s where Claude Code and Sol actually meet.

The launcher is a shell function called claudex. It launches Claude Code with the proxy configuration pre-loaded, keeping all the proxy environment variables isolated in a subshell so your normal claude command stays completely untouched.

It provides two profiles:

ProfileMain SessionSubagentsBackground
BalancedSolTerraLuna
All SolSolSolSol

Add this function to your shell configuration file (.zshrc on macOS, .bashrc on Linux):

claudex() (  set -eu  profile="${CLAUDEX_PROFILE:-balanced}"  case "${1:-}" in    balanced|all-sol)      profile="$1"      shift      ;;  esac  key_file="${XDG_CONFIG_HOME:-$HOME/.config}/claudex/proxy-key"  if [ ! -r "$key_file" ]; then    printf 'Missing proxy key: %s\n' "$key_file" >&2    exit 1  fi  proxy_key="$(tr -d '\r\n' < "$key_file")"  if [ -z "$proxy_key" ]; then    printf 'Proxy key is empty: %s\n' "$key_file" >&2    exit 1  fi  unset ANTHROPIC_API_KEY  unset ANTHROPIC_MODEL  unset CLAUDE_CODE_USE_BEDROCK  unset CLAUDE_CODE_USE_VERTEX  unset CLAUDE_CODE_USE_FOUNDRY  export ANTHROPIC_BASE_URL="http://127.0.0.1:8317"  export ANTHROPIC_AUTH_TOKEN="$proxy_key"  export ANTHROPIC_CUSTOM_MODEL_OPTION="gpt-5.6-sol"  export ANTHROPIC_CUSTOM_MODEL_OPTION_NAME="GPT-5.6 Sol via CLIProxyAPI"  export ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION="OpenAI Codex OAuth through a local proxy"  export ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES="effort,xhigh_effort,max_effort"  export CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1  export CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY="${CLAUDEX_CONCURRENCY:-3}"  export ENABLE_TOOL_SEARCH=false  case "$profile" in    balanced)      export CLAUDE_CODE_SUBAGENT_MODEL="gpt-5.6-terra"      export ANTHROPIC_DEFAULT_HAIKU_MODEL="gpt-5.6-luna"      ;;    all-sol)      export CLAUDE_CODE_SUBAGENT_MODEL="gpt-5.6-sol"      export ANTHROPIC_DEFAULT_HAIKU_MODEL="gpt-5.6-sol"      ;;  esac  command claude \    --model gpt-5.6-sol \    --effort "${CLAUDEX_EFFORT:-high}" \    "$@")Shell

After saving, reload your shell:

source ~/.zshrc   # macOSsource ~/.bashrc  # Linux

Worth knowing — a few things about how this works:

  • Balanced mode uses Sol for your main conversation, Terra (faster, lighter) for subagent tasks, and Luna (fastest, cheapest) for background operations. Good for everyday work where you want Sol’s intelligence on the main task without burning through your Codex allowance on routine operations.
  • All Sol mode puts Sol everywhere. Consistent quality across the board, but concurrent subagents consume your allowance faster.
  • Effort levels control how much reasoning Sol applies. The launcher defaults to high. Override it with the CLAUDEX_EFFORT environment variable: medium, high, xhigh, or max.

Usage examples:

claudex balanced                           # Daily work, Sol main + Terra/Luna supportingCLAUDEX_EFFORT=xhigh claudex all-sol       # Complex tasks, full Sol everywhereclaude                                     # Normal Anthropic-backed Claude Code, unchanged

Because claudex runs in a subshell, exiting the session removes all the proxy variables. Your normal claude command is always there, pointing at Anthropic’s models, completely separate.


Step 6: Verify It Works

Two quick checks from a second terminal while CLIProxyAPI is running.

Check 1 — Proxy health:

KEY="$(tr -d '\r\n' \  < "${XDG_CONFIG_HOME:-$HOME/.config}/claudex/proxy-key")"curl -sS \  -o /dev/null \  -w 'Proxy HTTP status: %{http_code}\n' \  http://127.0.0.1:8317/v1/models \  -H "Authorization: Bearer $KEY"unset KEY

You should see:

Proxy HTTP status: 200

Check 2 — End-to-end through Claude Code:

claudex balanced -p 'Reply exactly: sol-ok'

If Sol responds with sol-ok, the full chain is working: Claude Code to CLIProxyAPI to Codex OAuth to Sol and back.

.

.

.

The Bigger Picture

Here’s what I found interesting about this experiment — and it goes beyond Sol specifically.

The pattern emerging is that the best results come from combining the right model with the right environment. Claude Code functions as a kind of universal cockpit — you can fly different engines through the same interface, and the cockpit’s instruments (system instructions, tool scaffolding, context management, skills) make every engine perform better than it would alone. A well-equipped cockpit improves the flight regardless of what’s generating the thrust.

If you have access to Sol through your Codex allowance, you can now use GPT-5.6 Sol in Claude Code with all the workflow benefits you’ve built up — your custom skills, your sub-agent patterns, your project-level rules. Everything carries over because the interface stays the same.

Try the setup.

Run your own comparisons.

I’m genuinely curious whether the gap I saw holds across different types of tasks — or whether there are cases where Codex’s minimal environment actually produces better output.

Let me know what you find.


References

• 10 min read • The Art of Vibe Coding

The Skill That Makes Claude Use Your Design System Without Being Told

The Skill That Makes Claude Use Your Design System Without Being Told

You extracted a design system from a site you admire.

Every color, every font weight, every spacing value — captured. You went further and pulled the full bundle: component blueprints, section patterns, working code examples, an instruction manual that tells a coding agent exactly how to build with the system.

Then you pointed Claude Code at the bundle, told it which file to read first, and watched it produce a page that genuinely looked native to the brand.

It worked.

(And if you’ve done this even once, you know how good that moment feels.)

I closed the session that night feeling like I’d cracked something. Opened a fresh one the next morning, typed the same kind of prompt — and watched the AI produce something I wouldn’t have shipped. Soft shadows. Rounded cards. The same elevator music it always defaults to. The bundle was sitting right there in the folder, but the fresh session had absolutely no idea it existed.

So I did what you’ve probably done too.

Re-attached the bundle. Walked the agent through the protocol again. Pointed it back at the instruction manual. The page came out on-brand — because I’d stood over the machine and guided every step.

Sound familiar?

Two-panel black-and-white comic — MONDAY: a developer high-fives an AI robot in front of a branded website with confetti, saying We did it The design system works — TUESDAY: same developer exhaustedly holding up cue cards reading USE THE DESIGN SYSTEM while the AI has a question mark and the monitor shows a generic template, captioned Memory of a goldfish

(If you’ve spent more time reminding the agent about your design system than actually building with it, you know exactly what I’m describing.)

Here’s the thing.

A design system you have to keep hand-delivering isn’t really reusable yet. Your extraction was right, and the bundle was right. What was missing — the last mile — was making the agent reach for it automatically.

This week we close that gap.

One skill converts the Part 2 bundle into something Claude Code auto-triggers on any UI work, so you describe the product and the brand shows up on its own.

.

.

.

Where the First Two Posts Left Us

Two weeks ago, in I Taught Claude to Steal (Ethically) a Design System I Actually Like, I extracted a site’s design tokens — colors, fonts, spacing — and packaged them as a single file a coding agent can follow. The paint.

Last week, in I Extracted a Website’s Entire Design System Using This Skill, I went further: capturing component blueprints, section layouts, hover behaviors, and an instruction manual that tells the agent how to build with the system. The furniture.

Both outputs are excellent references.

Both are thorough, well-organized — and they sit in a folder waiting for someone (you) to carry them to the agent and explain what they mean.

For a while I had a sticky note on my monitor — ferpetesake, a sticky note — that said “ATTACH THE BUNDLE.” That’s when I realized the workflow had a hole in it.

You’ve furnished the room beautifully.

But you still have to walk the AI into it every single time.

The third skill in this series takes that finished bundle and turns it into something the agent picks up on its own — before you say a word about which brand to use.

.

.

.

The Idea: Package the System as a Skill

Here’s the shift in one line:

Instead of a folder you point at, you get a design system skill Claude Code already knows when to trigger.

Think about the difference between a reference binder on a shelf and a reflex. The binder might be thorough, beautifully indexed — but someone has to walk over, pull it down, and open it to the right page every time. A reflex fires the moment the situation calls for it. No conscious effort.

That’s what “auto-trigger” means in practice.

The skill carries a short description of when it should fire — any page, section, hero, button, card, or styling task — and Claude reads that description and applies the brand without being told which file to open. The wiring is built into the skill itself.

Whiteboard-style illustration showing three horizontal lanes — Manual: a stick figure running back and forth between a folder and a confused AI robot every session — Convert: folder goes through a funnel and becomes a star badge labeled Skill — Automatic: dev sits at a desk typing while the AI robot with the skill badge outputs a branded website, captioned describe the product brand follows

There’s also a stronger mode — more on this in a moment — an opt-in switch that makes this brand the sole design system for the entire project, so the AI can’t quietly wander back to generic defaults even if it wanted to.

.

.

.

Setup: One Install, One Command

Install the skill

One line, same shape as the prior two installs:

npx skills add nathanonn/agent-skills --skill design-system-to-skill --agent claude-code
VS Code terminal showing the npx skills add command installing design-system-to-skill — an ASCII SKILLS banner, the source repo, Found 10 skills, and Installation complete confirmation with the skill sitting next to the Part 1 and Part 2 skills in the file tree

Same repo as the first two parts. One-time cost. (Note the “runs with full agent permissions” caveat at the bottom — review the skill before use, as with any agent tool.)

Point it at the Part 2 bundle

The invocation takes one argument — the design system folder that the previous extraction produced:

/design-system-to-skill Turn this design system into a skill: @.design_systems/doodler
Claude Code terminal showing the slash command invoked on the doodler bundle — Claude narrates its plan, validates the bundle, and the worker returns structured JSON with slug doodler and name doodler-design-system

That folder is all the skill needs. A deterministic worker handles the mechanical staging: validating the bundle, copying assets, wiring the trigger. The AI does the authoring; a script does the plumbing.

The conversion at a glance

Here’s the shape of the whole thing, start to finish:

Minimal black-on-white flowchart showing four stages left to right — Design system bundle, Validate, Write auto-trigger plus MUST-USE wiring, and a solid black box labeled Per-brand Skill

Four stages.

Feed it a bundle, it verifies the bundle is real, it writes the trigger wiring that makes the brand auto-apply, and out comes a finished per-brand skill.

You point, it converts.

.

.

.

The Heart: The Skill Writes Its Own Trigger

Stay with me — this is the conceptual payoff.

The reason the conversion produces something genuinely reusable.

A per-brand skill is only useful if the agent knows when to reach for it. So the conversion writes that “when to reach for me” note as the very first thing it does: a trigger description that names the brand, the source site, the visual feel, what the skill reads, and concrete phrases that should fire it.

Claude Code diff view of the generated SKILL.md — a red line showing the DESCRIPTION placeholder being replaced by a green block with the authored trigger description naming the Doodler brand, its source site, its design feel, and trigger phrases, followed by the MUST-USE managed block being written into the project CLAUDE.md

The placeholder gets replaced with a detailed description: this skill captures the Doodler brand, it comes from a specific source site, it reads the component catalog and design tokens, and the agent should trigger it on phrases like “build a landing page,” “make a pricing page,” “design a hero section,” “style this component,” or “use the Doodler design system.”

Then the MUST-USE block gets written into the project’s guide file. That’s the wiring that makes the brand auto-apply in every future session — and the reason the demo prompt in a few paragraphs never names the skill.

.

.

.

What You Get

The finished skill on disk

Let me make “a skill” concrete.

VS Code showing the doodler-design-system skill folder expanded — assets/snippets with reference HTML files for buttons, cards, and sections, plus references folder with COMPONENTS.md open in the editor showing a button contract with Confidence high, Evidence 13 instances per 1 page, anatomy details, and a Variants by states table binding to design tokens

The finished folder contains everything a coding agent needs to build on-brand:

  • Reference HTML snippets — working code for each component (buttons, pricing cards, hero sections, testimonials) that the agent reads as a construction reference
  • Component catalog — anatomy, variants, states, and usage rules for every piece in the system
  • Design reference — the full token layer from Part 1 (colors, type scale, spacing, radii)
  • Token export — machine-readable values in a standard format

The series has layered up: a single file (Part 1) became a full bundle (Part 2) and now becomes a reusable design system skill Claude Code picks up automatically.

An honest note about MUST-USE

MUST-USE is opt-in — it’s off by default.

Turning it on makes this brand the sole, authoritative design system for the entire project. Every other design system skill you’ve installed goes off-limits for UI work there. The skill warns you which ones will be affected before you commit.

That exclusivity is the feature. When one project serves one brand (the common case), MUST-USE is what stops the AI from drifting back to generic defaults between sessions. The agent can’t “forget” the brand or quietly substitute its own guesses, because the brand is the only option.

If you juggle multiple brands in a single repo, leave it off and trigger the skill by name instead. But for most projects — one product, one look — turning it on is exactly what you want.

.

.

.

The Real Test: One Plain Prompt, No Skill Named

Here’s where the whole series pays off.

Fresh session. Empty context. The entire ask is a product brief — no mention of Doodler, the design system, or any skill name:

“Create a Multipage SAAS website (in HTML) for the following idea: Devlog turns a folder in your project into a real board — no server to run, no account to make, no extra tab to keep open. Claude Code reads and writes it directly while you work.”

Claude Code fresh session at ctx 0 percent showing a plain product prompt — Create a Multipage SAAS website for Devlog — with no mention of Doodler, the design system, or any skill name, and an In CLAUDE.md indicator in the bottom right

And here’s the moment that makes the conversion worth building.

Without any mention of Doodler in the prompt, the agent recognized this as UI work, loaded the design system skill on its own, and started reading the component contracts and snippets — all before writing a single line of code.

Claude Code auto-loading the Doodler design system skill — the agent says Since this involves UI work I must use the Doodler design system as required by the project instructions, then Skill doodler-design-system Successfully loaded skill, followed by reading the design system data and component contracts

The first time I typed a prompt and watched Claude load the design system on its own — without me saying a word about Doodler — I sat there for a second. It felt like the difference between giving someone directions every time and them just knowing the way.

👉 I specified the product. The brand showed up by itself.

How it stayed on brand

Here’s what the build summary reported.

Claude Code build summary showing Built in devlog-site using the Doodler design system as the sole authority, a Pages table listing 5 HTML files with purposes, and a How it stays faithful to Doodler section citing exact color hex values, typography choices, 4px ink outline rule, and token-resolved radii

Five pages — a landing page, features, pricing, docs, and about — each built from the same design system. The summary listed every brand rule the agent followed: the color palette, the type choices, the signature card borders, the component patterns. All pulled from the extracted tokens, applied consistently across every page.

The agent read the system and reported what it honored.

.

.

.

The Renders

Let me show you the output.

(This is the part I kept refreshing the browser for.)

The hero. The whole brand identity — visible in one shot. Navigation style, canvas color, hand-drawn elements, headline typography, accent colors. All matching the source site, on a product that never existed there.

The rendered Devlog landing page hero in the Doodler brand — a floating capsule navigation with Devlog star mark, a peach pastel canvas with hand-drawn wavy doodles in the corners, a mint Built for Claude Code pill, a large Clash Display headline reading Your project board lives in your repo, dual CTAs, and a 5-star social proof line

The feature grid. Six cards, all built from the same component patterns as the source site. Consistent borders, consistent icons, consistent typography — on every card.

The Devlog features section showing a 6-up grid of white cards each with a thick ink outline border, mint-accented line icons, Clash Display card titles like Plain-text tickets and Versioned by git, Inter body text, and the floating nav pill above

The pricing section. Three tiers with an inverted emphasis card for the featured plan. Pricing tables are the classic component AI tends to botch — this one rendered correctly, with the right highlight treatment and accent placement.

The Devlog pricing page showing the full section — a PRICING eyebrow, a bold headline The board is free Always, supporting copy, and three complete pricing tier cards with feature lists and CTAs: Solo at zero dollars forever, Pro in an inverted dark card with a mint MOST POPULAR pill at 8 dollars per month, and Team at 5 dollars per repo per month

The full-page scroll. This is the one that seals it. A still image can show that one section looks right; continuous motion shows that every section holds the same brand from top to bottom.

Animated scroll through the full Devlog landing page from hero to features to a split section with a Kanban board mock — every section maintaining the Doodler brand with peach canvases, ink-outline cards, mint accents, and hand-drawn doodles

Hero to features to pricing to a split section with a Kanban board mock — peach canvases, ink-outline cards, mint accents, hand-drawn doodles between sections. Unbroken. Coherent.

The page feels native to a brand it was never built for, on a product that never existed on that site. And the prompt never named the brand.

.

.

.

Where We Are Now

Three weeks, three layers:

  • A website you admire became a DESIGN.md — the paint (Part 1)
  • That file grew into a full design system bundle — the furniture (Part 2)
  • The bundle became an auto-triggering skill — the reflex (Part 3)

The previous extraction gave you the system. This conversion makes agents use it without you standing over them. You describe the product; the brand follows.

And there’s one more thing I want to show you — but that’s next week.

.

.

.

Your Move

Here’s the complete path, start to finish:

  1. Install the skill:
    npx skills add nathanonn/agent-skills --skill design-system-to-skill --agent claude-code
    
  2. Point it at a Part 2 bundle — the design system folder sitting in your project’s design systems directory.
  3. Decide on MUST-USE. On for single-brand projects (the common case). Off if you juggle multiple brands in one repo.
  4. In a fresh session, describe what to build. The brand shows up on its own. The agent reports which rules it honored.

The skill is open source at github.com/nathanonn/agent-skills — same repo as Parts 1 and 2.

Design has always made me sweat. Seriously — my method for years was embarrassingly manual: find a site I liked, open DevTools, and squint at values until my eyes crossed. This series turned that squinting habit into a real pipeline, from a URL to a design system skill that Claude Code reaches for automatically — about 25 minutes of total extraction and conversion time.

The last mile of a design system is getting the AI to use it without a reminder. Now it does.

Go build something on-brand.

• 11 min read • The Art of Vibe Coding

I Extracted a Website’s Entire Design System Using This Skill

I Extracted a Website's Entire Design System Using This Skill
Watch the video walkthrough, or read the full written guide below.

I ran the token extraction on a site I’d been eyeing using the “extract-design-md” skill.

The colors landed perfectly — exact hex codes, correct font weights, spacing on point. I felt good about it.

Then I put my page next to the original, and every single component was a stranger wearing the right outfit.

Cards came out with soft shadows instead of thick borders. The navigation stretched edge to edge where the original floated as a rounded capsule over the hero. The accent color showed up everywhere instead of the one or two spots where it actually belonged.

Same palette. Different furniture.

Last week, in I Taught Claude to Steal (Ethically) a Design System I Actually Like, I showed how to capture a site’s design tokens — colors, fonts, spacing values — and package them in a file a coding agent can follow. The approach works: exact values mean the AI stops guessing at shades of blue and rounding corners to the wrong radius. But tokens are the paint. They tell Claude what color the walls should be. They say nothing about how the furniture is built.

Imagine walking into a room that’s been repainted to match a showroom you love. Wall color, trim, floor tone — every surface is right. But the furniture is from a completely different store: chairs the wrong shape, shelves too tall, light fixtures from a different catalog entirely.

That’s token-only extraction in a nutshell.

Two cards side by side — left labeled "Tokens only" shows a card with the right colors but a generic soft shadow, right labeled "Full design system" shows the same card with a thick distinctive border matching the source brand

This post fixes that.

One skill, one command, and you can extract a design system from a website — the paint and the furniture — in a package a coding agent can build from.

.

.

.

What Tokens Can’t Tell You

Think of a token file as a box of labeled paint cans and a ruler.

It tells you “use this shade of dark ink” and “round the corners this much.”

Useful.

But it can’t tell you how anything is built.

Here’s the thing: I ran a test — same prompt, same source site, tokens only — and watched the AI get four things wrong when all it had was the palette.

Cards. The source site uses a thick, bold border on every card — the kind of deliberate outline that makes each piece pop off the page. The AI defaulted to a subtle shadow instead. Same card shape, completely different feel.

Side-by-side comparison — left card labeled "What the AI built" with a soft drop shadow, right card labeled "What the source looks like" with a thick dark border, with an annotation highlighting the structural difference

Navigation. On the source, the nav floats as a rounded capsule over the hero image. The AI stretched it edge to edge like a standard website header — a completely different structural decision that changes the whole feel of the page.

Side-by-side comparison — left shows a full-width header bar stretching edge to edge, right shows a floating capsule-shaped navigation over a peach-colored hero, with an annotation pointing out the difference

Hover behavior. Hovering over a button on the source triggers a gentle fade. The AI made buttons darken or grow on hover, which feels like visiting a different site entirely.

Side-by-side comparison — left shows a button with a darkening hover effect, right shows a button with a gentle opacity fade on hover, demonstrating different interaction feels

Pricing emphasis. One pricing plan on the source flips to a dark background to stand out from the rest. The AI highlighted it with a bright accent color instead — a different visual strategy for the same goal.

Side-by-side comparison — left shows a pricing card highlighted with a bright accent color, right shows a pricing card with an inverted dark background, showing different emphasis approaches

These are design choices — about how each piece is built, how it behaves, and where specific elements belong.

A palette can’t capture structure, behavior, or placement rules.

(If you’ve ever been pleased with a rebuild and then held it up next to the original — and felt that quiet sinking “oh, that’s off” in your stomach — you know exactly what I mean.)

Last week’s skill was designed for a different job: giving Claude exact values so it stops guessing at colors and spacing. And it does that well. But when you want the AI to build pages that genuinely look like they belong to the source — matching shapes and behaviors along with colors — you need more than a palette.

That’s what the new skill captures.

.

.

.

One Install, Same Setup

Stay with me — the setup is fast.

Two tools and one install, same as last week if you already set those up.

1. playwright-cli — the browser engine that reads the pages and captures screenshots. Install it once:

npm install -g @playwright/cli@latestplaywright-cli install --skills

2. Firecrawl (optional but recommended) — gives the skill better page discovery, so it samples more than just the homepage. Without it, the skill falls back to thinner link detection. If you need the free setup, I covered it in How to Run Firecrawl for Free in the Cloud (No Credit Card, No API Keys).

3. The skill itself:

npx skills add nathanonn/agent-skills --skill extract-design-system --agent claude-code
Terminal zooming in as the install command appears — npx skills add nathanonn/agent-skills with the extract-design-system skill and claude-code agent flags, in a fresh project with an empty file tree

If you set up last week’s skill, the only change is the skill name. Same repo, same install shape.

This skill includes everything the previous one did — it still extracts colors, fonts, and spacing — and then goes further by capturing how every component is actually built. Use it when you want pages that match at every level; use last week’s version when you only need the color and typography reference.

(Three commands. Less than a minute.)

.

.

.

The Extraction: URL In, Design System Out

Let me show you what happens when you point it at a real site.

/extract-design-system https://doodler-landing.webflow.io
Claude Code with the slash command entered and zoomed in — /extract-design-system https://doodler-landing.webflow.io — pointing the skill at a live Webflow site

Worth knowing: Doodler is a Webflow cloneable template under a Creative Commons Public Domain License (CC0). We’re working with a freely licensed design here — no gray area about copying someone’s live business. You can clone the same template on Webflow and follow along if you want.

Here’s what happens after you hit enter. Five phases, each building on the last.

Five-phase flow diagram showing the extraction pipeline — URL goes through Discover pages, Read tokens, Capture components, Assemble bundle, and Validate, producing a complete Design System Bundle

1. Discover — The skill maps the site and picks a handful of representative pages to study. The homepage alone won’t show everything — forms, pricing tables, and blog layouts live on other pages, and you need that variety to capture the full system.

2. Read tokens — Colors, fonts, and spacing values pulled straight from the page’s styles. This is the same foundation last week’s skill built. Exact values read from real CSS, nothing estimated from screenshots.

3. Capture components — Here’s what’s new. The skill goes through every sampled page and records how each button, card, input field, and navigation bar is actually built. What elements sit inside each one. What variations exist — a bold version, a subtle version, an accented version. How each piece behaves when you hover over it or click it. And any design rules worth preserving, like “that thick border is the brand’s signature.”

(This is everything that token-only extraction misses — the structural DNA of the design.)

4. Assemble — Packages the whole extraction into a single folder you can hand to a coding agent. Inside: a design reference, component blueprints, working code for each piece, and a set of instructions that tells the agent exactly how to use everything. The instructions are the critical addition — they turn a reference folder into something an agent can follow step by step.

5. Validate — Checks its own work before handing anything over. The design reference gets quality-checked. Example components get rendered and compared against the live site. And 71 test assertions ran against the actual source to verify color accuracy, component structure, and layout fidelity. Nothing ships until every gate passes.

The whole extraction — from URL to a fully validated design system — finished in under 20 minutes.

Nineteen minutes.

Terminal showing the extraction complete — checkmark and "Design system extracted" confirmation, all four validation gates passed, self-test results showing 71 passed with 0 failed on the live source
Plain-English summary of the extraction results — 24 tokens, 7 atoms including buttons and cards, 8 section patterns, all 4 validation gates passed, total time of 19 minutes 6 seconds

24 tokens. 7 component types. 8 section patterns. 71 self-test assertions, all passing. Ready to use.

.

.

.

What You Get: The Bundle

Here’s what lands in the output folder.

Folder tree showing the design system bundle structure — a root folder containing the design reference, component blueprints, example code with a visual gallery, the instruction manual, token export, and quality checks

The design reference — everything from last week’s extraction (colors, fonts, spacing) expanded with a catalog of every component the site uses. The complete style guide, structured for a machine to follow. An agent reading this gets the same understanding of the brand that a designer would get from a printed brand book.

Component blueprints — for every button, card, input field, and page section: what’s inside it, what variations exist, how it behaves on hover and click, and which design rules are sacred. The blueprints capture things like “that thick border is the brand’s signature — never replace it with a shadow.”

Example code — working samples of each component, plus a visual gallery that renders them all side by side. This is the proof layer — you or the AI can compare against the source at a glance and verify that the extraction got the details right.

(I spent an embarrassing amount of time clicking between the gallery and the live site. They matched.)

And here’s the kicker: the instruction manual. A step-by-step protocol that tells a coding agent what to read first, what rules to follow, and which elements are non-negotiable. The agent follows a playbook — and in my testing, this single piece was the biggest factor separating “close enough” from “looks native.”

Token export + quality checks — the raw design values in a standard format for other tools, plus the tests the skill used to verify its own output — reusable for validating pages you build from the system later.

.

.

.

The Real Test: Build a Page From It

Time to prove it works.

I opened a fresh project — empty folder, no prior context — and handed Claude Code the extracted bundle with a simple request: build a SaaS landing page for a fictional product called Devlog, a project board that lives inside your repo.

The build prompt being typed into Claude Code — "Create a SaaS landing page using this design system" with the extracted bundle's instruction manual attached as a reference, followed by the Devlog product idea

What happened next showed exactly why the bundle matters.

The playbook came first. The first time I pointed Claude Code at the bundle with the instruction manual, it did something I hadn’t seen before: it read the entire protocol — design reference, component blueprints, example gallery — before writing a single line.

Claude Code reading the design system bundle — consuming the design reference, component blueprints, and gallery examples per the instruction manual's protocol before writing any markup

When the page came out with the exact same thick-bordered cards and floating capsule nav, I realized what had been missing all along. A plan — an actual set of instructions telling the agent how each piece was supposed to be built.

Blueprints shaped every component. Feature cards came out with the source’s bold border. The navigation floated as a capsule. The mint accent appeared in exactly the right spots. These matched because the agent had blueprints, with no guessing.

A self-report sealed it. The agent listed which brand rules it followed — thick outlines, floating nav, doodle accents, mint reserved for punctuation. You can verify at a glance that it matched the source.

Claude Code's build report showing "Brand non-negotiables honored" — 4px ink outline on feature cards, white rounded nav pill, hand-drawn doodles, mint reserved for punctuation only

Under the hood, the extracted tokens landed in the generated page as real values — color names, radius values, spacing units, all pulled from the original site and wired directly into the source code.

Generated HTML showing the extracted design tokens wired in as CSS custom properties under the root element — ink, surface, accent, peach, and coral colors plus the radius scale and spacing values

And here’s the result.

A full landing page — hero, features, how-it-works, pricing, call to action, and footer — that looks like it was built by the same designer who built the original site.

Full-page scroll of the generated Devlog landing page — hero with floating nav pill over peach canvas, feature cards with bold borders, how-it-works section on a green panel, pricing cards, mint CTA band, and footer — all native to the Doodler brand

Every card has the right border, and the nav floats as a capsule. The mint accent shows up in exactly the right place and nowhere else. The page feels native to the Doodler brand, on a product that never existed on that site.

👉 That’s the distance between having the paint and having the furniture. Last week’s tokens got the colors right. This week’s bundle got everything right.

.

.

.

Your Move

Design has always made me sweat.

I’m a developer — logic and code, that’s my lane. My actual method for borrowing a look (ferpetesake) was embarrassingly manual: find a site I liked, open DevTools, and squint at hex codes until I got maybe 70% of the way there. This skill does in 19 minutes what I used to fail at in an afternoon.

Any time you want to extract a design system from a website you like, the process is four steps.

  1. Install the skill.
npx skills add nathanonn/agent-skills --skill extract-design-system --agent claude-code

  1. Point it at a site you admire — one you have the right to reference. Same principle as last week: your own site, a client’s site, or a freely licensed template you plan to make your own.



  2. Hand the bundle to Claude Code. Point the agent at the instruction manual and describe what you want to build. The agent reads the playbook, follows the blueprints, and reports what it honored.



  3. Build something new that matches at every level. The buttons, the cards, the sections, the brand rules — the paint and the furniture.


The skill is open source at github.com/nathanonn/agent-skills — same repo as last week’s token extraction.

Here’s how the two skills fit together.

Last week’s post gave you the paint — exact colors, fonts, and spacing values that make a site look like itself. This post gave you the furniture — components, layouts, behaviors, and a set of instructions that tells a coding agent how to assemble everything.

Together, that’s the complete design system — captured from a real site in under 20 minutes, packaged for a coding agent, and validated before you ever use it.

If you tried token-only extraction and felt like the output was close but off, the palette was always right. The missing piece was how each component was built. Now you have both.

The Doodler extraction took 19 minutes. The landing page took 5 more. Under half an hour from a URL to a brand-native page — and zero squinting at DevTools.

Go extract one.

• 15 min read • The Art of Vibe Coding

I Taught Claude to Steal (Ethically) a Design System I Actually Like

I Taught Claude to Steal (Ethically) a Design System I Actually Like
Watch the video walkthrough, or read the full written guide below.

You open Claude Code and ask for a landing page.

You hit enter, feeling optimistic.

A few seconds later you’re looking at a centered hero, a soft gradient, three rounded feature cards, and a “Get Started” button that could belong to any of ten thousand other apps.

Generate another one.

You get its cousin.

You know this feeling. The output is competent, clean, and completely anonymous — the visual equivalent of elevator music.

(If you’ve never stared at a freshly generated UI and thought “this is fine, I guess,” congratulations. The rest of us are not so lucky.)

.

.

.

The Reason Every AI-Generated UI Looks the Same

Here’s what nobody tells you about asking an AI to design something from a blank prompt.

The model is designing from memory.

Not your memory — the averaged-out blur of a million bootstrapped SaaS sites it absorbed during training.

Ask it for “a modern landing page” and it hands you the statistical middle of everything it has ever seen. That’s why the results feel so familiar. You’re staring at the mean of the internet.

No amount of prompt-wrangling fixes a sampling problem.

You can say “make it bold” or “make it premium,” and you’ll get a slightly bolder, slightly more premium version of the same average.

Here’s the thing: I’m a developer. A logic-and-code person. Design has always made me sweat. So my actual method for years has been embarrassingly manual — find a site whose look I admire, open DevTools, and reverse-engineer it by hand.

Eyedropper the colors, squint at the font sizes, guess at the spacing. I’d get maybe 70% of the way there before I ran out of patience and shipped something “good enough.”

(Every time I tried this, I’d get through the colors and fonts, feel good about myself, then open a fresh page on the site and discover a completely different card style I’d missed. The spec was always half-finished.)

There’s a better move.

Instead of asking an AI to invent a design, you hand it a real design system — one you already like — and make it build inside that system. The trick is capturing the system in the first place, which used to be the tedious part.

That’s the whole story of this post.

We’re going to extract a DESIGN.md from a website, get a clean, reusable spec, and then prove it works by handing that file to Claude Design and watching it build a brand-new page that still looks like the original.

.

.

.

A Site’s Design Is Already Written in Its CSS

Most “AI, look at this design” workflows start by feeding a screenshot to a vision model and asking it to guess.

That’s slow, expensive, and lossy.

Here’s the thing a lot of people miss: a website’s visual identity is already written down, in exact values, inside the page.

The primary color is a real hex code. Font sizes and weights sit in the type scale as real numbers. Corner radius and spacing rhythm live as real pixel values inside CSS custom properties and computed styles.

You don’t need a model to look at a screenshot and estimate — you can read the numbers directly, for free.

That’s the insight behind the extract-design-md skill. It reads a site’s design tokens straight out of the CSS, and only uses screenshots to inform the prose — the “this feels like a premium broadsheet” descriptions that help an agent apply the system with taste.

The output format is DESIGN.md, an open spec from Google Labs (it has picked up over 23,000 GitHub stars). One file combines two things:

  • YAML front matter — the machine-readable design tokens (colors, typography, radius, spacing, components).
  • Markdown prose — the human-readable rationale that tells an agent why those values exist and how to use them.

Tokens give an agent exact values. Prose gives it judgment. Together they form a persistent design memory you can hand to any coding agent.

If this sounds familiar, it should. Back in My App Looked Like Everyone Else’s Until I Discovered This Claude Skill Trick, I built a reusable design system by hand — generating variants, documenting them, and packaging the result as a Claude Skill. This skill automates the hardest part of that whole dance: capturing a real system so you have something to reuse in the first place.

.

.

.

Six Stages, URL to DESIGN.md

Before we run it, here’s the shape of the whole thing at a glance.

A minimal black-on-white flow diagram showing six stages left to right — 1 Discover pages, 2 Extract tokens, 3 Harvest components, 4 Capture feel, 5 Synthesize, 6 Validate — starting from a URL and ending in a solid black box labeled DESIGN.md

Six stages take a URL and hand back a validated DESIGN.md:

  1. Discover pages — map the site and pick a few representative pages (home, pricing, auth, blog) so the tokens come from real variety.
  2. Extract tokens — read the CSS variables and computed styles; resolve colors to hex; name them by role rather than dumping every shade.
  3. Harvest components — capture the buttons, inputs, and cards the pages actually expose, plus their hover and focus states.
  4. Capture feel — take light screenshots that inform the written descriptions only.
  5. Synthesize — map everything onto the DESIGN.md schema in a consistent order.
  6. Validate — run the official linter as a quality gate.

The design decisions inside those stages are what make the result trustworthy. Tokens come from real CSS values, so nothing gets invented.

Components get captured only when the page genuinely has them. And when a site has no cards or chips, the skill records that absence honestly instead of hallucinating a component to fill the gap.

(The honesty is the whole feature. You get what’s really there, with a note about what isn’t.)

.

.

.

Two Tools and One Install

Three moving parts, and only one of them is strictly required.

1. playwright-cli — the browser engine that does the reading and the screenshots. The install --skills step registers it as a skill for Claude Code so the agent can drive a real browser. Install it once:

npm install -g @playwright/cli@latestplaywright-cli install --skills
VS Code terminal showing the playwright-cli help output — a list of core browser commands like open, goto, click, fill, hover, screenshot — confirming the CLI is installed and on PATH

2. Firecrawl — used for the page-discovery step. This one is optional but recommended. Without it, the skill falls back to thinner link discovery; with it, you get better page coverage.

If you don’t already have Firecrawl running, I wrote a full free setup guide in How to Run Firecrawl for Free in the Cloud (No Credit Card, No API Keys). The one thing to add is a short CLAUDE.md block that points Claude Code at your local instance so it uses Firecrawl instead of the built-in web tools.

VS Code showing a CLAUDE.md file open with a Firecrawl instructions block — rules telling Claude Code to always use Firecrawl skills, target the localhost:3663 service, and avoid the cloud status check

3. The skill itself — one command:

npx skills add nathanonn/agent-skills --skill extract-design-md --agent claude-code
Terminal showing the npx skills add command installing extract-design-md into Claude Code — an ASCII "SKILLS" banner, the source repo github.com/nathanonn/agent-skills, and a "Repository cloned" confirmation

That’s the whole setup. One-time cost, then it’s a single command per site from here on.

.

.

.

The Live Run

I pointed the skill at my own website.

/extract-design-md https://www.nathanonn.com/
Claude Code prompt showing the /extract-design-md command run against https://www.nathanonn.com with Opus 4.8 and auto mode on

Quick honesty note on why I used my own site. Design tokens — a hex value, a font size — aren’t really the kind of thing anyone owns. But cloning a real business’s homepage pixel-for-pixel is a different and dumber move.

The sane lane: your own sites, client sites you have rights to, or a site you use as a starting point that you then make your own. I picked mine so nobody has to email me about it. Moving on.

Claude verified the three tools, created an output folder, and mapped the site.

Claude Code starting the extraction — verifying that playwright-cli, npx, and Firecrawl are all available, then creating the output folder and running firecrawl map on the site

Then it did the one thing I wish more tools would do. It stopped and asked me to confirm which pages to sample before spending any time crawling.

Claude Code presenting a page-sample confirmation — an auto-picked list of 5 pages (homepage, newsletter, contact, a blog post, archives) with options to use all 5, drop the contact page to 4, use homepage only, or type a custom set

It auto-picked five pages and offered me the choice. I dropped the contact page — it’s a trivial layout that would only add noise to the sample, and the newsletter page already covered the form inputs.

(I’ve used enough tools that just barrel ahead without asking. This one stopped, showed me its thinking, and let me trim the list. If every agent did this, I’d trust them twice as fast.)

From there it ran mostly hands-off.

I’m going to spare you the play-by-play — the output is what matters here. It worked through token extraction across the pages, harvested the components, took a light screenshot pass to inform the writing, and then ran a lint check.

Claude Code at the screenshot stage — capturing viewport screenshots for home, newsletter, blog, and archives pages, then scraping the homepage copy to inform the prose
Claude Code running the design.md linter — npx @google/design.md lint on the output file, catching a letterSpacing value that needs fixing and a few non-blocking warnings

A couple of minutes later, it was done.

Here’s the final report.

Claude Code's final extraction report — DESIGN.md written, 4 pages sampled (home, newsletter, blog, archives, with contact dropped), light theme only, 9 colors, 6 type roles, 4 radius values, components found and missing listed, and a clean lint result with 0 errors

The report is refreshingly specific about what it did and didn’t find:

  • Pages sampled: 4 — home, newsletter, blog post, archives (contact dropped from the auto-picked 5).
  • Theme: light only. No dark toggle on the site, so a single file.
  • Colors: 9, a monochrome system — black as the primary CTA, grays for text and borders, white and light-gray surfaces, a small neutral ramp. No colored accent, because the site genuinely doesn’t use one.
  • Type roles: 6, all in Inter, from a 48px/700 heading down to a mono label style.
  • Radius + spacing: a 4-step radius scale and a full spacing scale on a 1200px container.
  • Components found: primary button (with hover), secondary button, input field (with focus), link.
  • Components missing: cards and chips — the site has none, so none were invented.
  • Lint: 0 errors after a couple of automatic structural fixes, plus 8 non-blocking warnings and one contrast warning it correctly flagged as a false positive.

.

.

.

The Result: A DESIGN.md You Can Actually Read

Let me show you the artifact.

The generated DESIGN.md file open in an editor — YAML front matter with a name and description, a colors block where each color carries a hex value plus a comment showing its computed rgb() and raw oklch() source, and a typography block defining an Inter type scale for h1, h2, h3

One file. Two layers.

The YAML front matter holds the machine-readable design tokens — every color, type size, radius, spacing value, and component definition the skill extracted. The markdown prose below it holds the human-readable rationale: what the design feels like and how an agent should apply it.

Here’s the full token layer from the extraction:

---version: alphaname: Nathan Onn — Vibe Coding Newsletterdescription: Minimal high-contrast editorial identity for a solo developer newsletter — black-on-white typography, a single black CTA, and quiet gray tonal panels.colors:  primary: "#000000" # black — filled CTA button  primary-hover: "#374151" # gray-700 — CTA hover (computed rgb(55,65,81))  on-primary: "#ffffff" # button / on-black text  surface: "#ffffff" # page background  surface-muted: "#f3f4f6" # gray-100 — split-screen side panel / tonal blocks (computed rgb(243,244,246))  text: "#111827" # gray-900 — headings, body, links (computed rgb(17,24,39))  text-muted: "#6b7280" # gray-500 — supporting copy, labels (computed rgb(107,114,128))  border: "#6b7280" # gray-500 — input borders  neutral-50: "#f9fafb" # gray-50  (raw: oklch(98.5% .002 247.839))  neutral-100: "#f3f4f6" # gray-100 (raw: oklch(96.7% .003 264.542))  neutral-300: "#d1d5db" # gray-300 (raw: oklch(87.2% .01 258.338))  neutral-700: "#374151" # gray-700 (raw: oklch(37.3% .034 259.733))typography:  h1:    fontFamily: "Inter, sans-serif"    fontSize: 48px    fontWeight: 700    lineHeight: 60px    letterSpacing: 0em  h2:    fontFamily: "Inter, sans-serif"    fontSize: 30px    fontWeight: 700    lineHeight: 36px    letterSpacing: 0em  h3:    fontFamily: "Inter, sans-serif"    fontSize: 24px    fontWeight: 600    lineHeight: 32px    letterSpacing: 0em  body:    fontFamily: "Inter, sans-serif"    fontSize: 16px    fontWeight: 400    lineHeight: 1.6    letterSpacing: 0em  label-caps:    fontFamily: "Inter, sans-serif"    fontSize: 14px    fontWeight: 600    lineHeight: 1.25    letterSpacing: 0.05em # uppercase eyebrow labels ("LATEST ISSUE")  mono:    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace"    fontSize: 14px    fontWeight: 400    lineHeight: 1.5    letterSpacing: 0emrounded:  none: 0px  md: 0.375rem # 6px — buttons, inputs  lg: 0.5rem  xl: 0.75remspacing:  xs: 0.5rem # 8px  sm: 0.75rem # 12px  md: 1rem # 16px  lg: 1.5rem # 24px  xl: 2rem # 32px  container: 1200px # max content widthcomponents:  button-primary:    backgroundColor: "{colors.primary}"    textColor: "{colors.on-primary}"    typography: "{typography.body}"    rounded: "{rounded.md}"    padding: 12px 24px  button-primary-hover:    backgroundColor: "{colors.primary-hover}"    textColor: "{colors.on-primary}"    rounded: "{rounded.md}"    padding: 12px 24px  button-secondary:    backgroundColor: transparent    textColor: "{colors.text}"    typography: "{typography.body}"    rounded: "{rounded.md}"    padding: 8px 16px  input-field:    backgroundColor: "{colors.surface}"    textColor: "{colors.text}"    typography: "{typography.body}"    rounded: "{rounded.md}"    padding: 12px 16px  input-field-focus:    backgroundColor: "{colors.surface}"    rounded: "{rounded.md}"    padding: 12px 16px  link:    textColor: "{colors.text}"    typography: "{typography.body}"---

Every value traces back to real CSS on the page. Colors are named by role — primary, surface, text-muted, border — so an agent knows what job each color does. Typography is a real scale extracted from computed styles. Components carry their exact padding, radius, and state changes.

When the site had no cards or chips, the file records that honestly instead of inventing them.

Below the tokens, the prose section describes the overall identity and guides an agent on how to apply the system with taste — things like “nothing competes with the words” and “let black do the pointing.”

And here’s the kicker: the file doesn’t stop at documentation. The official @google/design.md CLI can export it into working code:

# Turn the DESIGN.md into a Tailwind theme (v4)npx @google/design.md export --format css-tailwind DESIGN.md > theme.css

It also exports to a Tailwind v3 config or to W3C DTCG tokens. The spec that documents your design system can drop straight into a real project.

.

.

.

The Real Test: Claude Design

A design spec is only worth something if a model can build a consistent new design from it.

So let’s test it somewhere the original site has never been seen.

Enter Claude Design, Anthropic’s design tool (currently in beta). It collaborates with you on polished visual work — prototypes, pages, slides.

The key part for us: it can build production-ready UI from your own design system. That makes it the perfect testbed, because it only knows what’s in the file.

I uploaded the DESIGN.md, chose the newly launched Claude Sonnet 5, and asked for a landing page for a fake developer tool:

Create a landing page using the attached DESIGN.md for the following SAAS: Devlog turns a folder in your project into a real board — no server to run, no account to make, no extra tab to keep open. Claude Code reads and writes it directly while you work.

Claude Design's "What will you design today?" screen with the Devlog prompt typed in, the DESIGN.md file attached, and the model set to Claude Sonnet 5

It read the file and, like a good collaborator, came back with questions before building anything — which sections to include, what the primary CTA should do, whether the product was free or paid, and what tone the copy should take.

Claude Design asking clarifying scope questions for the Devlog landing page — which sections to include, what the CTA should do, whether it's free or paid, and what voice the copy should use

I answered a few and let it decide the rest. Then it went to work laying out the page.

Claude Design building the Devlog landing page — the hero taking shape with a black wordmark, an "OPEN SOURCE · LOCAL-FIRST" pill, the headline "A board that lives in your folder," and a terminal-style install snippet

Here’s where it landed.

A black wordmark, a black pill “Get started” button, an Inter headline, and a gray tonal panel showing a fake board.

A landing page for a fake developer tool called Devlog — black wordmark, a black pill "Get started" button, a bold Inter headline reading "A board that lives in your folder," and a gray tonal panel showing a kanban board — all generated by Claude Design from an extracted DESIGN.md

The rest of the page carried the same identity all the way down:

The Devlog "How it works" and "Features" sections — three numbered steps in Inter, then a grid of gray tonal feature cards with small caps labels, all black-on-white
The Devlog "Get started" section rendered as a full-width black band with a white Inter headline and a terminal-style install snippet with a Copy button
The Devlog FAQ section with plain expandable questions, followed by a minimal footer with the black wordmark and a "A board that lives in your folder" tagline

Look at what carried over: the single black CTA, the Inter type scale, the gray tonal cards, and the black band that mirrors the high-contrast feel of the source. The whole page reads like it belongs to the same family as my site, on a product that never existed there.

(It’s a strange feeling — recognizing your own site’s personality on a product that doesn’t exist. Like hearing someone hum a tune you wrote.)

Let me be upfront about expectations here.

My source site is a minimal black-on-white newsletter — clean, simple, intentionally restrained. So the output is also minimal and restrained. That’s exactly the point.

If you feed Claude Design a bold, colorful design system, you’ll get bold, colorful output. The tool mirrors whatever you give it. I gave it monochrome restraint, and it handed back monochrome restraint — on a product that never existed on my site.

👉 Consistency was the whole goal.

A design system’s entire job is to make new things look like they belong, and the file did exactly that on a page it was never built for.

It works.

.

.

.

Steal Like an Engineer

Here’s the pattern worth taking away, bigger than any single skill.

Stop asking AI to invent a design. Give it a real system to build within, and it stops averaging and starts applying. When you extract a DESIGN.md from a website you admire, you’re handing the model a specific point of view instead of a blank canvas.

A few ways to put it to work:

  • Brand consistency. Extract your own site once, drop the file in your repo, and every new page Claude Code builds inherits the look.
  • Client work. Capture a client’s existing system so AI-built additions match what they already have.
  • Honest inspiration. Point it at a site you like — one you have the rights or the reason to reference — and use the extracted system as a starting point you transform, rather than a one-to-one copy.

That last one is where the “ethically” in the title earns its keep. Tokens aren’t copyrightable, and wholesale cloning is a bad look. Use the sharp tool responsibly.

So here’s your move:

  1. Install the skill (GitHub): npx skills add nathanonn/agent-skills --skill extract-design-md --agent claude-code
  2. Point it at a design you actually like (that you have the right to reference).
  3. Hand the resulting DESIGN.md to Claude Code or Claude Design.
  4. Build something new that finally looks like something specific.

The next UI you generate doesn’t have to be the averaged-out memory of the entire internet.

It can look like a design you chose.

Go steal one. Ethically.

• 13 min read • The Art of Vibe Coding

Stop Losing Work When You Compact Claude Code (The Handoff-Doc Skill)

Stop Losing Work When You Compact Claude Code (The Handoff-Doc Skill)
Watch the video walkthrough, or read the full written guide below.

You know the moment.

You’ve been working for over an hour.

The status line says ctx:22% (that’s about 220k tokens in Opus). You know you should compact. You also know you should write something down first — a handoff doc, a note, anything that captures where you are so the next session can pick up cleanly.

But writing a handoff prompt from scratch, right now, mid-session? That means composing the prompt. Reviewing the output. Making sure it caught the decisions you made forty minutes ago, the file you changed twenty minutes ago, the test that’s still failing.

So you skip it.

I did.

Last month, I was two hours deep in a refactor, ctx sitting at 24%. I compacted without writing anything down. The next session spent its first fifteen minutes re-reading files I’d already inspected — and then made a decision I’d already made. In a different direction.

Fifteen minutes of rework. Because I didn’t want to spend thirty seconds.

That’s the gap.

The gap between “I should capture my state” and “I actually do it” is where session continuity goes to die.

There’s a skill that closes it.

One command. The skill reads back through the session, distills what happened, writes it to a file on disk, and reports what it captured. Thirty seconds of your time, about three minutes of processing. Then you compact with the handoff doc already durable — written to disk, safe from the reset.

The next session reads it back. Picks up cold. Knows exactly where you left off.

This post walks through the whole thing: the threshold that triggers it, five steps from “context is getting high” to “fresh session, state preserved,” and why the skill captures more than you’d write manually.

.

.

.

The System This Builds On

In Never Let Claude Code Auto-Compact Again, I built a full context management system: a status line that shows ctx% on every turn, operating zones from green to red, structured /compact instructions using a KEEP/SUMMARIZE/DROP template, and a rehydration checklist for after compaction.

At the center of that system was the HANDOFF.md habit — write a structured document to disk before compacting so the session state survives the reset.

It was also the step most people skip.

And honestly? I get it. Generating a good handoff required an eight-line prompt. You had to remember to run it. You had to review the output. When you’re already deep in a session and context is climbing, that friction adds up fast.

The handoff-doc skill replaces the entire manual step with one command.

If you haven’t read the prior post, that’s fine — this one is self-contained.

But the full context management playbook is there for anyone who wants the complete system.

.

.

.

Install the Skill

The handoff-doc skill is open source and available from the agent-skills repo.

Claude Code (plugin marketplace):

/plugin marketplace add nathanonn/agent-skills/plugin install handoff-doc@nathanonn-agent-skills

Other agents (Codex, Cursor, GitHub Copilot):

npx skills add nathanonn/agent-skills --skill handoff-doc

For manual installation or the full list of available skills, see the repo README.

Once installed, /handoff-doc is available as a slash command in Claude Code. In other agents, describe the task and the skill triggers automatically.

.

.

.

The 20% Rule

For 1M Opus sessions, 20% is already 200,000 tokens of context.

That’s easily an hour or more of active development — file reads, tool calls, test output, back-and-forth.

The rule is simple: when ctx crosses ~20%, finish the current micro-task, invoke the handoff-doc skill, then compact.

Why 20% and not higher?

Quality degrades gradually. By 25-30%, the model is processing a growing pile of stale content on every turn — old exploration logs, resolved error output, abandoned approaches. The signal-to-noise ratio drops before you feel it in the responses.

The skill itself needs room.

It reads back through the session to distill what happened. Invoking it at 20% gives it space to work. At 30%, the distillation is competing with the content it’s trying to capture.

Early resets force a healthy rhythm. Working in 15-20% chunks means each new session starts fresh, with full model quality. The Claude Code handoff doc bridges the gap between sessions — capturing enough state that the fresh session can continue without re-reading the transcript. The work stays continuous even though each chunk gets a clean window.

Think of it as version control for your session state. Git captures code state between commits. The handoff doc captures working state between context resets. Both let you move forward with confidence that what you’ve done is preserved.

The threshold is a heuristic — a zone to start watching for a clean boundary. Some sessions run lean and don’t cross 20% for two hours. Others — heavy file reading, broad research, lots of tool calls — blow past it in thirty minutes.

The status line makes it visible. You glance at ctx:21% the way you glance at a battery indicator.

When you see it, that’s the cue.

Status line showing ctx:25% — the moment you notice the threshold has been crossed

.

.

.

Five Steps From Threshold to Reset

Here’s the full workflow. Five steps, start to finish.

Step 1: Notice the threshold

You’re mid-session.

The status line shows ctx:25%. Past the 20% zone. Time to capture state before compacting.

This is a glance — completely passive. If you set up the status line from the prior post, you see ctx% on every turn without running any command. The moment it crosses your threshold, you know.

Step 2: Invoke the skill

Type /handoff-doc followed by the destination path.

Invoking the handoff-doc skill with a destination folder

The @ reference lets you point at any folder in your project. The skill checks whether the destination already has files and follows their naming convention. If the folder uses a YYYYMMDD_NN_slug pattern, the skill continues the numbering automatically. If the folder is empty, it starts the series.

(The first time I ran this in a folder that already had three handoff docs, it picked up the naming pattern and slotted in as number four. A small thing — but it meant I never had to think about filenames. The skill just knew.)

One line.

That’s the entire invocation.

Step 3: Review the handoff doc

The skill reads back through the session, distills what happened, and writes a structured document to disk. Processing takes about three minutes, depending on session length.

When it finishes, the output reports what it captured:

Skill output showing the handoff doc summary and file location

The generated doc follows a consistent skeleton: what happened this session, where things live (files changed, key paths), verification done — and honestly, what was not verified — git state, and open follow-ups with numbered next steps.

The handoff doc opened in the editor, showing structured sections

Take thirty seconds to skim the key sections. The structure is deliberate — each section answers a question the next session will have:

  • What happened tells the next session what was accomplished and what decisions were made.
  • Where things live gives it the file paths and locations it needs to navigate the work.
  • Verification done is the honesty check — what was tested, what passed, and critically, what was not tested. This prevents the next session from assuming something works when it hasn’t been verified.
  • Git state anchors the handoff to a specific commit, branch, and push status.
  • Open follow-ups are the actual continuation point — numbered, specific, ready to pick up.

That verification section deserves a callout. One of the first times I used the skill, the handoff doc flagged — plainly, almost embarrassingly — that a feature I’d been working around hadn’t actually been tested. I would have glossed right over that in a manual handoff. The skill was more honest than I would have been.

If those sections look right, move on.

The skill has full session context to work from — it usually captures more than you’d remember to include manually.

Step 4: Compact

With the handoff doc written to disk, run /compact. You can add custom summarization instructions (the prior post has KEEP/SUMMARIZE/DROP templates for this), or run it bare.

Here’s the important shift: the handoff doc is the durable artifact now. It lives on disk, outside the context window, unaffected by whatever the compaction summarizer decides to keep or drop. The compact summary adds useful compressed context, but the handoff doc on disk is the lifeline.

This is also where the /compact instruction from the prior post pays off. If you point the compact instruction at the handoff doc — “Focus on the handoff doc at [path], current git diff, and next step” — the summary and the handoff doc reinforce each other.

Running /compact after the handoff doc is saved

Compaction takes one to two minutes for a session around 25%. The progress indicator shows how far along it is.

Compaction in progress — progress bar at 55%

Step 5: Fresh context, handoff preserved

After compaction completes, ctx drops to 0%.

And here’s the payoff.

Post-compaction: ctx at 0%, handoff doc re-read, skills restored

Look at the compaction output. It lists every file it re-read — including the handoff doc you just created. Skills are restored. Referenced files are loaded back into context.

The session is fresh. Your state is preserved. The model knows what was done, what’s pending, and what to do next — all from the handoff doc it just read back.

Continue from exactly where you left off.

.

.

.

Why This Beats Writing It Manually

Manual handoffs fail in three predictable ways.

You skip it entirely.

This is the most common failure mode — and the one I’m most guilty of.

When context is high and the session has been long, writing a handoff prompt feels like overhead you can defer. The compact summary might be good enough. You’ll remember what you were doing. You’ll just re-read the git diff.

Then the next session opens, and you spend fifteen minutes re-establishing context that could have been preserved in thirty seconds. Re-reading files. Re-explaining decisions. Worse — sometimes the session makes different decisions because it doesn’t know what you already decided.

The skill makes the habit frictionless enough that you actually do it.

One command instead of a prompt you compose from scratch every time.

The activation energy drops from “write an eight-line prompt and review the output” to “type one line and skim for thirty seconds.”

You write too little.

A manual prompt like “create a handoff doc” gives the model almost nothing to work with.

The result is a vague summary that misses specifics: which files were changed and why, what was tested and what wasn’t, where the git state stands, which follow-ups are still open.

With the skill, a structured skeleton does the work for you.

Each section — what happened, where things live, verification done, git state, follow-ups — exists because it answers a question the next session will have. The structure acts as the prompt.

You write too much.

A full transcript replay disguised as documentation.

Every step narrated, every error included, every tangent preserved. The next session wades through three pages when it needed three paragraphs.

The skill distills. It extracts what the next session needs to continue — decisions made, current state, open work — and drops the noise. Exploration paths that led nowhere get summarized or omitted. Error output you already resolved is gone. The handoff reads like a briefing, because that’s what it is.

Beyond those failure modes, the skill handles details you’d forget in a manual prompt. It checks the destination folder for existing naming conventions. It auto-increments the sequence number. It converts relative time references to absolute dates so the doc stays useful a week later. Every Claude Code handoff doc has the same structure, the same level of detail, the same honesty about gaps.

.

.

.

Making It a Habit

The workflow is three steps, once it’s in muscle memory:

  1. Watch ctx%. The status line makes this passive — you see the number on every turn without doing anything.
  2. At ~20%, invoke the skill. Finish the current micro-task first, then /handoff-doc with a destination path.
  3. Compact and continue. The handoff doc is on disk. Compact. The fresh session re-reads it automatically.

A few tips for adoption.

Pick a consistent destination. Use notes/handoff_docs/ or docs/handoffs/ — whatever fits your project structure. The skill handles a dedicated folder with sequential files, and it handles a single HANDOFF.md at the project root. Choose one pattern and stick with it so the skill builds a clean series over time.

Don’t review obsessively. Thirty seconds of skimming after the skill runs is enough. If the key sections are present — decisions, files changed, next step — move on. The skill has full session context. It captures more than you’d remember to write.

Pair it with Compact Instructions. If you have a Compact Instructions block in your CLAUDE.md (described in the prior post), the skill and those instructions work together. The skill writes the durable state to disk. The compact instructions shape what gets preserved in the compressed session summary. Two layers of continuity.

Use it before /clear too. The workflow pairs naturally with /compact, but it works equally well before /clear — when you want a genuinely fresh session instead of a compressed one. The handoff doc is the bridge. The reset mechanism is your choice.

Let the series accumulate. If you use a folder with sequential naming, the handoff docs become a session log. Over the course of a multi-day project, you end up with a numbered series that traces how the work evolved: what was done in each session, what decisions changed, what follow-ups carried forward. It’s useful for your own reference, and it’s useful for onboarding someone else into a project mid-stream.

One more thing worth stating: the handoff doc works for human readers too. If you need to hand a project to a colleague — or pick it up yourself after a week away — the handoff doc reads like a structured briefing. You don’t need Claude Code to benefit from what it wrote.

.

.

.

The Habit in One Sentence

When context crosses the threshold, capture your state with one command, then reset.

The handoff-doc skill makes this automatic. Session state goes to disk. Context resets clean. The next session picks up cold from one structured document that tells it everything it needs to continue.

The best sessions — the ones that actually ship — are the ones where context stays intentional from start to finish. The status line is the sensor. The threshold is the trigger. The handoff-doc skill is the mechanism that makes the whole thing effortless.

Build the habit. Watch ctx%. Invoke the skill. Compact. Continue.

For the full context management system — status line, operating zones, compact instructions, rehydration checklist — start with Never Let Claude Code Auto-Compact Again.

For readers new to Claude Code skills, Claude Skills: Your “I Know Kung Fu” Moment Has Arrived (Part 1 of 3) covers how to build and use them.

The handoff-doc skill (and several others) is open source at github.com/nathanonn/agent-skills.

• 11 min read • The Art of Vibe Coding

Generate Images in Claude Code (using Codex plugin)

Generate Images in Claude Code — Without Even Asking for a Command
Watch the video walkthrough, or read the full written guide below.

You’re three hours into a Claude Code session.

The feature works. The tests pass. All that’s left is the picture at the top — a featured image, a hero illustration, something to make the thing look finished.

So you ask for it. Plainly, the way you’d ask a teammate sitting next to you:

“Generate an image of a minimalist line-art lighthouse on a dark navy background.”

And Claude Code apologizes.

Claude Code responding to a plain image request with "The image generation isn't something I can do — I don't have an image generation tool available in this environment," then offering SVG line-art or ASCII art instead

It offers to write you an SVG. Or some ASCII art.

Helpful — in the way a hardware store is helpful when you walked in for a sandwich.

I’ve done this more times than I want to admit — alt-tabbed to Codex for a quick image, got sidetracked tweaking the prompt, and came back to Claude Code ten minutes later having forgotten what I was about to commit. That little shrug is the whole problem in one frame. The capability you want lives one tool over, in Codex. The usual move is to alt-tab away to go get it — and the flow you spent three hours building quietly evaporates the moment you leave the window.

By the end of this post, that same sentence — no command, no tool-switch — produces a real PNG sitting neatly inside your project.

And the way it gets there opens a door bigger than any single image.

.

.

.

Why Claude Code Can’t Make Images (And Codex Can)

Let’s be fair to Claude Code first.

The limitation is a deliberate product boundary.

Claude Code is a coding harness — it edits files, runs commands, reasons about your codebase, and wires things together. Picture generation was simply never wired into that toolset. The model underneath can reason about images perfectly well; the harness around it just has no tool to make one.

So Claude does the honest thing and tells you.

You can feel how badly people want that gap filled.

Search around and you’ll find a small industry of “how to generate images in Claude Code” guides — an MCP server here, an external CLI there, a paid wrapper somewhere else. When that many workarounds exist for one missing capability, the demand is obvious. And so is the native answer, which is still no.

Codex took the other road.

In April 2026, OpenAI shipped gpt-image-2, a purpose-built image model, and made it the default for image work in Codex — a clear step up from the version it replaced, with sharper output and the ability to reason about a layout before it draws. Within weeks the older DALL-E models were retired from the API entirely, which left the new model as the whole story.

Codex exposes it through a built-in skill you trigger with $imagegen.

You describe what you want, Codex generates it, sizes it, and saves it.

Native.

So here’s the state of Claude Code image generation in mid-2026: the thing you want exists, it’s excellent, and it’s sitting in the tool next door. Keeping Codex open in a second terminal works — but it drags back the exact switching tax I wrote about all the way back in Claude Code vs Codex: Why I Use Both (And You Should Too).

Copy a path, switch windows, lose your place, switch back.

There had to be a way to borrow the capability without leaving home.

.

.

.

The Bridge Already Exists — And It Already Works

There is.

I’ve written about it before.

Back in April I covered Codex Reviews My Code Inside Claude Code — But I Don’t Trust It Blindly — OpenAI’s official Codex plugin that runs Codex inside a Claude Code session. That post used it for code review. But the plugin reaches well past review — it’s a general bridge to Codex, and one of its commands, /codex:rescue, can hand an arbitrary task to Codex and let it run.

Which left me poking at an obvious question: if the plugin gives me Codex inside Claude Code, and Codex has $imagegen… can I get a real image generated without ever leaving Claude Code?

So I tried it.

I called /codex:rescue and told it — in so many words — to use $imagegen for the lighthouse.

Claude Code prompt invoking /codex:rescue with the instruction "use the $imagegen to Generate an image of a minimalist line-art lighthouse on a dark navy background"

Codex woke up, took the task, and got to work.

The codex:codex-rescue subagent running inside Claude Code and reporting "Done" — 2 tool uses, 10.0k tokens, 2m 11s

A couple of minutes of waiting while Codex did its thing in the background, and a finished image existed — without a single window switch. The terminal I was working in never lost focus. The session I’d spent three hours building never broke.

It worked. Here’s what came out:

The generated lighthouse — minimalist white line-art on a flat dark navy background, beam lines radiating from the lantern room, gentle waves at the base

A real image, generated by gpt-image-2, without leaving Claude Code.

Proof of concept — done.

.

.

.

But You Have To Know The Magic Words

Here’s where the proof-of-concept stops being something you’d actually want to use day to day. Two frictions — and they’re the whole reason this post has a part two.

Friction one: it only fires when you summon it by name.

/codex:rescue is a command you have to remember and type, phrased just so. Ask for the image the way a human naturally asks — the plain sentence from the very top of this post — and nothing happens. Claude Code tells you it can’t make images and offers you that SVG again. The capability is reachable, but only if you already know the secret handshake. Forget the handshake and you’re back at the shrug.

Friction two: the file lands wherever Codex feels like dropping it.

Run the rescue route and the image shows up loose at the root of your project, sitting right alongside your config and your docs.

Project root file tree showing the generated lighthouse-line-art.png dropped in alongside AGENTS.md, CLAUDE.md, JOT.md, and MANIFEST.md

One stray PNG next to your project files is no crisis.

But picture a real working afternoon: a hero image for the landing page, two illustrations for a docs page, a placeholder avatar, a quick thumbnail to test a card layout. Five generations, five files scattered across your project root. By the third experiment I had four stray PNGs sitting next to my CLAUDE.md. I caught myself doing a manual cleanup and thought: this is exactly the kind of chore a script should handle.

There’s a sharper trap hiding in that route, too.

The first time I tried scripting this myself, I stared at a blank output for a solid ten minutes before I realized the shell had eaten the dollar sign. The $imagegen token starts with $, so the shell quietly expands it to nothing before Codex ever sees it — the command runs fine, no image appears, and nothing tells you why.

So here’s the scoreboard after the experiment: the capability is real and it runs in-session, but reaching it means knowing a command most people won’t, and using it means cleaning up after it. That’s exactly the kind of rough seam worth wrapping once — so you never feel it again.

.

.

.

The Fix: Say It In Plain English, Get A Tidy File

This is the part I want you to steal.

I built a small skill — codex-imagegen — that wraps the whole messy path into something that responds to how you’d actually ask.

Install it, then type the same natural sentence you’d have typed anyway.

No /codex:rescue.

No command to memorize:

Generate an image of a minimalist line-art lighthouse on a dark navy background.

I half-expected to have to type /codex-imagegen the first time. Instead I just asked for the image the way I normally would — and watched Claude load the skill on its own. That was the moment I knew the wrapper was worth building.

Claude Code automatically loading the codex-imagegen skill from the same plain-English prompt, running the bundled script with the codex plugin runtime, and reporting the image saved to .codex-image/lighthouse.png

Same words that earned a shrug two sections ago. This time: “Successfully loaded skill,” the plugin runtime picked automatically, and a finished PNG — 1200×1200, flat navy #0d1b2a — saved to .codex-image/lighthouse.png.

No handshake.

And look where the file went.

Project file tree showing the image neatly inside a dedicated .codex-image folder rather than loose at the project root

Instead of cluttering your project root, the image lands in .codex-image/ — its own corner, out of the way, easy to find later.

For readers new to skills: a skill is a small, reusable capability you teach Claude Code once and trigger by describing what you want. (I wrote a whole series on them, starting with Claude Skills: Your “I Know Kung Fu” Moment Has Arrived (Part 1 of 3).)

Here’s what the skill handles so you don’t have to:

Without the skillWith codex-imagegen
Remember and type /codex:rescue with the right phrasingAsk in plain English; the skill triggers on intent
Fish the file out of the project rootLands organized in a .codex-image/ folder
Works only when the plugin is set up just soUses the plugin if present, falls back to the Codex CLI if not

If you’d rather be deliberate, the explicit slash form /codex-imagegen "your prompt" is there too.

Most of the time you won’t reach for it — plain language is the point.

That fallback matters more than it sounds. It means the skill works on a teammate’s machine that only has the Codex CLI, or on a server with no plugin installed, with zero changes on your end. And the shell-eats-the-dollar-sign trap from earlier? Never reaches you. The script handles that token safely every single time.

One quiet bonus: Codex keeps a local cache of every image it generates and never cleans it up. The skill tidies that cache behind the scenes after each run, so the folder doesn’t quietly balloon to hundreds of megabytes while you’re not looking.

.

.

.

What This Unlocks: An Image Generator Other Skills Can Call

Here’s where it gets interesting — and why I think the natural-language trigger matters more than the convenience.

Because the skill responds to plain intent instead of a hard-coded command, it stops being something only you invoke. It becomes a building block other skills and workflows can lean on.

Picture a publishing workflow that notices a finished post has no featured image, writes a prompt from the post’s own title, calls codex-imagegen, and drops the result in the right folder — no human in the loop. Or a project scaffolder that fills in placeholder icons and hero art as it sets up a new repo, instead of leaving you a wall of empty boxes to fill later. Or a slide-deck skill that generates a custom illustration for each section as it builds the outline, so the deck arrives already looking like someone designed it.

Whiteboard-style hub-and-spoke diagram: three skills (Blog Publisher, Project Scaffolder, Slide Deck Builder) each pointing inward to a central codex-imagegen box, which outputs a cluster of images into the project

In every one of those, image generation is no longer a thing you stop and do by hand. It’s a step another skill takes on your behalf, mid-flow, because the door is now wide enough for a machine to walk through.

The principle underneath: a capability wrapped as a natural-language skill becomes composable.

One skill can hand off to another, and image generation turns into a primitive your automations reach for — rather than a manual detour you take by hand. None of that works if the only way in is a command a human has to remember to type.

I’ve been building exactly this kind of skill-calls-skill workflow, and it deserves its own post to do it justice. I’ll walk through a real one soon — if you don’t want to miss it, subscribe.

.

.

.

Install It (It’s Open Source)

The skill is open-source, in my agent-skills collection. Two ways in, depending on your setup.

For Claude Code, install the packaged plugin:

/plugin marketplace add nathanonn/agent-skills/plugin install codex-imagegen@nathanonn-agent-skills

For any other agent — Codex, Cursor, Copilot, or Claude Code itself — use the open Agent Skills CLI:

npx skills add nathanonn/agent-skills --skill codex-imagegen

One prerequisite: the skill rides on Codex, so you need either the Codex plugin in Claude Code or the Codex CLI installed locally (npm i -g @openai/codex, then codex login). Auth runs off your OpenAI account. If neither runtime is present, the skill tells you exactly what to install rather than failing silently.

And because it ships in the cross-agent collection, the same skill works outside Claude Code too. Same plain-language trigger, in whichever agent you happen to be living in that day.

.

.

.

The Harness Gap Keeps Closing

Step back and the trend line is hard to miss.

A year ago, the honest answer to “Claude Code or Codex?” was both, in two terminals. Then the Codex plugin let one tool review the other’s code in a single session. Now a small skill lets Codex generate images inside Claude Code — and not as a command you summon, but as something Claude reaches for the moment you simply ask.

The move worth internalizing: when your main tool can’t do something, you don’t always have to switch tools or wait for a feature to ship.

Sometimes you borrow the capability from the tool next door and wrap the seam so cleanly that you — and your other skills — stop noticing it was ever a seam. The Codex plugin opened that door. A skill that triggers on plain language walks the rest of the way through it.

Claude Code image generation went from impossible here to just ask in the span of one small skill.

That’s the whole shift — a feature that used to require leaving the room now happens without you breaking stride.

So install it, open your next project, and ask for the image you need — in plain English, the way you’d ask anyone. Watch it land in your project a couple of minutes later. Then go build the rest of whatever you were making, and tell me what came out.

• 11 min read • The Art of Vibe Coding

The Three Files That Made Codex /goal Reliable Enough to Walk Away From

Person wondering "Can I walk away?" beside three documents — GOAL (what to build), VERIFY (how to prove it), and PROGRESS (what happened) — feeding into Codex /goal, producing a completed app. Caption: "Trust comes from evidence, not vibes."

The Hard Part Was Walking Away

The first time I ran a codex goal command on something that mattered, I sat there for twenty-eight minutes pretending to check email while the terminal scrolled.

I wasn’t doing other work. I was watching.

Making Codex write code has never been the hard part.

The hard part is walking away — stepping out of the room while an autonomous agent builds something you actually care about, with no way to know if it’s going sideways until it’s done.

Over five weeks and five builds, the same question kept surfacing: How do I know it did the right thing while I was gone?

Here’s what I learned.

A good /goal skill gives Codex a clear system to follow — and gives you something concrete to audit after the run ends. That system comes down to three files: GOAL, VERIFY, and PROGRESS.

  • GOAL defines what “done” means.
  • VERIFY maps each requirement to an actual check.
  • PROGRESS records what happened during the run so you can review the evidence instead of guessing.

Together, they turn a hands-off Codex run from “hope it works” into “review the receipts.”

Person wondering "Can I walk away?" beside three documents — GOAL (what to build), VERIFY (how to prove it), and PROGRESS (what happened) — feeding into Codex /goal, producing a completed app. Caption: "Trust comes from evidence, not vibes."

.

.

.

Five Builds, One Pattern

This pattern came from building things — running the codex goal command across five projects and watching what actually made the difference between a run I could trust and one I couldn’t.

BuildScaleTrust Lesson
WordPress plugin1 goalCodex needs a clear finish line
CLI tool2 goalsConnected goals need clear verification
Browser game8 goalsSequencing matters
Expanded game7 goalsSeam checks catch hidden bugs
WooCommerce plugin10 goalsLong runs need receipts

Each build gave Codex a bigger slice of autonomous work. Different stacks, different scales, anywhere from 28 minutes to nearly five hours of runtime. The trust structure underneath stayed the same.

Here’s the thing. The first build — one goal, 28 minutes — was small enough to verify by hand. By the fifth — ten goals, nearly five hours — manual verification would have taken longer than the build itself. (Stay with me on that: a five-hour autonomous run where you come back and check the receipts instead of babysitting. That’s the payoff these three files unlock.)

The full walkthrough for each build is linked at the end of this post.

.

.

.

Does Codex Know What Done Means?

GOAL answers the first trust question: What exactly are we building?

Without a clear definition of done, Codex invents its own finish line. It wanders, second-guesses scope, and eventually gives up having shipped half the requirements. Every long run that went sideways in my five weeks traced back to one thing: the spec was too vague for Codex to grade its own work.

And Codex re-reads the goal text constantly. It uses that same document as both the to-do list and the test for “done.” A vague spec gives it nothing to grade against — so it either keeps going in circles or declares victory on a hunch.

A good /goal skill solves this by writing GOAL from evidence. It reads your project first — the language, the framework, the folder layout, the naming patterns already in place. Then it asks targeted questions, each carrying a recommended answer and a one-line reason. By the time it generates GOAL.md, the document is grounded in your actual codebase.

GOAL.md should include:

  • Objective — one sentence describing what this goal produces
  • Repo context — what the skill learned by reading the project
  • Requirements — the specific features or behaviors to build
  • Assumptions — what the skill inferred and confirmed with you
  • Boundaries — what Codex should not touch
  • Definition of done — the outcomes that constitute “finished”
  • Stop conditions — when Codex should stop and ask instead of guessing

That last one matters more than it looks.

Stop conditions are the guardrail that prevents Codex from filling in gaps with assumptions. When the spec runs out of detail, a good skill tells Codex to pause rather than improvise.

In practice, the skill handles most of this for you. It shows up with a draft of what “done” looks like and asks you to confirm or edit — which is always faster than writing it from scratch. (The whole exchange feels like confirming a restaurant reservation. “Table for one? Near the window? 7 PM?” Yes, yes, yes.) Most of the time, the recommended answers are right. When they’re wrong, editing one line is cheaper than discovering the gap mid-run.

A messy feature request passes through a funnel (read repo, clarify assumptions) and becomes GOAL.md — with objective, requirements, boundaries, definition of done, and stop conditions — pointing toward a checkered finish flag. Caption: "GOAL tells Codex what done means."

.

.

.

Proof, or Just a Green Terminal?

VERIFY answers the second trust question: How do we know the work is correct?

GOAL defines what done looks like. VERIFY maps each requirement to an actual check that proves it was built correctly. Those checks have to use real commands from your project — real test runners, real build steps, real linters. Invented checks are worse than no checks, because they hand you false confidence.

A green terminal is comforting.

But if the check doesn’t trace back to a specific requirement in GOAL, it proves nothing useful. The WooCommerce build had ten goals running across nearly five hours. Without explicit traceability from each requirement to its proof, a clean summary could easily mask three missing features — and you’d only discover them after deploying.

VERIFY.md should include:

  • Requirement-to-check mapping — each GOAL requirement paired with its verification method
  • Real commands — test, build, lint commands that exist in the project
  • Manual checks — for anything that can’t be automated (UI polish, UX flow), explicitly listed as manual
  • Expected results — what a passing check looks like
  • Environment notes — anything the checks depend on (ports, services, seed data)
  • Seam checks — for multi-goal runs, checks that test the boundaries between goals

Seam checks earn a highlight.

I almost skipped the full-loop check in the Ion Viper build — every individual goal had passed its own tests. Why bother? Four hidden bugs at the transitions between goals is why. Stale state on restart, timing conflicts between systems, projectiles accumulating silently across scene boundaries. Each goal looked fine in isolation.

The full loop revealed the assumptions that no single goal’s tests could catch.

👉 A green terminal is not proof if the check doesn’t map back to the requirement.

Three requirements linked by chains to VERIFY.md, which maps each to a proof step. An arrow leads to Codex running verification with a "No fake checks" callout, producing a PASS stamp. Caption: "VERIFY turns done into proof."

.

.

.

What Happened While You Were Gone

PROGRESS answers the third trust question: What happened while I was gone?

GOAL gives the standard. VERIFY gives the proof plan. PROGRESS is the running record of what Codex actually did — what it changed, what it checked, what passed, what broke, and how it responded.

For a quick, single-goal run, you might glance at the terminal and move on. For a ten-goal build that ran for nearly five hours, terminal output is useless as an audit tool. PROGRESS.md is the structured receipt that lets you review the entire run without scrolling through hundreds of lines of terminal history.

PROGRESS.md should include:

  • Goals started and completed — with timestamps
  • Files changed — what Codex touched in each goal
  • Checks run — which verification steps executed
  • Results — pass/fail for each check
  • Errors found and fixes made — what broke and how Codex handled it
  • Evidence paths — where to find the artifacts (test output, screenshots, logs)
  • Remaining issues — anything Codex flagged but couldn’t resolve
  • Resume point — where to pick up if the run is interrupted
  • Final summary — the overall status in one paragraph

Resume points matter for long runs.

If a build fails at goal 7 of 10, you don’t want to re-run goals 1 through 6. PROGRESS records exactly where the work stopped and what state it was in, so the next run picks up cleanly.

The WooCommerce build was the first time I actually left the desk. Nearly five hours. I came back, opened PROGRESS.md, and had the full story in under three minutes — what passed, what broke, what Codex fixed on its own, and what it flagged for me to look at. A few minutes of structured review instead of hours of babysitting. That’s the trade these files offer.

A developer walks away from a desk. A robot works on code. PROGRESS.md records: Goal 1 PASS, tests run, bug fixed, evidence saved, resume point. A human returns and reviews the document with a magnifying glass. Caption: "PROGRESS is why walking away is not blind trust."

.

.

.

The Chain That Holds It Together

The three files work because they connect.

  • GOAL defines what done means.
  • VERIFY maps those requirements to proof.
  • PROGRESS records whether the proof held up during the actual run.

Any important requirement should be traceable across all three.

Here’s the traceability test: pick any requirement from GOAL. You should be able to find its matching check in VERIFY, the expected result, the recorded outcome in PROGRESS, and the evidence a human can review. If the chain breaks at any link — and I’ve had it break — you catch it during review, before anything ships.

Let me show you what that looks like.

A GOAL requirement says “admin users can generate auto-login links.” VERIFY maps that to a specific test command plus a manual browser check. PROGRESS records that both passed, with the test output saved to a file path you can open. From that single thread, you or anyone reviewing the build can verify the claim without re-running anything.

Each file fills a role the others can’t cover. GOAL without verification is a wish list — requirements with no accountability. A proof plan with no execution record can’t confirm the tests actually ran. And an execution log with no standard to measure against is a diary that tells you what happened but can’t tell you whether it was right.

The traceability between them is what turns three documents into a system you can rely on. Hand a reviewer the three files from any goal, and they can reconstruct the full story: what was supposed to happen, how it was supposed to be tested, and what actually happened. The evidence sits on disk.

.

.

.

Where Trust Still Breaks

The biggest risk is an unclear spec.

I wrote “build a wishlist plugin” once — three words, no detail. The result had wishlist functionality, technically. Just not the ones I needed. (If you’ve ever gotten back exactly what you asked for and realized the problem was what you asked for, you know the feeling.)

That costs real time and money.

The WooCommerce plugin build used roughly $131 worth of subscription usage across ten goals and nearly five hours. A vague spec that forces a second attempt doubles that cost. The three files pay for themselves by making the first run more likely to be the only run.

Codex also doesn’t do design polish. It builds things that work, but the look and feel comes out plain. Admin interfaces are functional and ugly. That’s fine — the hours the three files freed up from implementation are hours you can spend on refinement instead.

And I still review everything.

The three files don’t replace human judgment. They move it to better places: before the run, you define the right goal. After the run, you audit the receipts and polish what needs a human eye.

The codex goal command handles the middle part.

Your job is the beginning and the end.

.

.

.

Trust Is Engineered

The goal was always the same: give Codex a system that leaves evidence you can audit.

GOAL sets the finish line, VERIFY maps the proof plan, and PROGRESS keeps the receipts.

Together, they turn a hands-off run from “hope it works” into “review the evidence.” That’s what made walking away possible — across five builds, five different stacks, and run times stretching from 28 minutes to nearly five hours.

The skills keep getting better, too. Every gotcha you teach one is a gotcha it handles next time. The second project on a given stack goes smoother than the first, and the fifth smoother still. The investment compounds.

The specific stack doesn’t matter, either. Whether you’re building WordPress plugins, CLI tools, browser games, or something else entirely, the three-file structure translates. Build the skill once for your domain, teach it your stack’s hard-won rules, and future projects start ahead.

Build the system once. Document the evidence. Review the receipts.

All four skills from this series are open source:

npx skills add nathanonn/agent-skills --agent codex

The repo is at github.com/nathanonn/agent-skills.

The series, if you’re catching up:


More workflows like this — AI-assisted development with Claude Code, Codex, and the tools between them — land in The Art of Vibe Coding newsletter every week. If this one was useful, the next one probably will be too.

• 14 min read • The Art of Vibe Coding

I Gave Codex a Requirements Doc and Got a CodeCanyon-Grade Plugin Back

Assembly line illustration for turning a requirements doc into a CodeCanyon-grade WordPress plugin with Codex /goal

The first time I used Codex /goal, I sat at my desk for twenty-eight minutes pretending to do other work while an autologin plugin built itself from a one-paragraph spec.

That was How to Use Codex /goal to Build WordPress Plugins (My Spec-to-Ship Workflow). One feature. One goal. The kind of experiment where you peek at the terminal every 90 seconds and try to look casual about it.

This time, my input was a full requirements document and this single line:

./run-goals.sh

Then I walked away. For nearly five hours.

When I came back, a complete WooCommerce plugin was sitting in the repo — an admin grid for bulk-editing stock quantities across products, including per-variation stock for variable products. That’s the exact kind of WooCommerce complexity that breaks naive implementations. The genre of plugin that sells on CodeCanyon for $30–60.

All built while I made dinner, watched half a movie, and checked the terminal exactly once. (More on that later.)

VS Code file explorer showing the starting state with only wp-requirements-to-goals skill, playwright-cli skill, and requirements.md — the entire human input is one requirements file plus two skills

Everything I’ve built with the codex goal command up to this point has fit inside a demo. The autologin plugin took twenty-eight minutes. How I Chained Two Codex /goal Runs to Build a Complete CLI Tool scaled the pattern to two linked goals. How I Used 8 Codex /goal Runs to Build a Browser Game From Scratch pushed it to eight.

The question I’ve been carrying — and maybe you have too — is whether /goal survives contact with real software. Multi-feature. Edge cases. Settings pages. The kind of product someone would actually pay for.

This post is where I find out.

The honest caveat lands early, same as always: /goal produced the code, but the requirements produced the outcome. And this time the spec was a full requirements document, decomposed by a skill into a layered tree of goals — each with its own contract, its own verification, its own proof.

(If you’re new to the series, the autologin post covers what /goal is and how the goal trio works. Everything here builds on that foundation.)

.

.

.

The Requirements Are the Real Work

A paragraph was enough for an autologin plugin.

A full product needs a full brief.

I learned this the hard way on a previous build. The requirements were loose enough that the agent met every acceptance criterion — and still missed what I actually wanted. (If you’ve ever written a Jira ticket and gotten back something that was technically correct and completely wrong, you know the feeling.)

That gap is where I started treating the requirements doc as the real product.

(Full requirements: https://github.com/nathanonn/wc-bulk-edit-stock/blob/main/requirements.md)

The requirements for this build carried tagged user stories with explicit acceptance criteria, edge cases around out-of-stock states and variable-product handling, and cross-cutting concerns like validation and save resilience:

  • US-01: Quickly update a single product’s stock from a filterable admin grid
  • US-02: Set a group of products to out-of-stock at once (bulk action)
  • US-03: Edit per-variation stock for variable products inline
  • Edge cases: WooCommerce inactive, concurrent edits, deleted staged products, 100+ variations
  • Cross-cutting: Save/validation resilience, filtering/search, batch selection

That doc is the product brief, the architecture, and the test plan — all in one file. The better it is, the less you touch the build.

I wrote about the upstream discipline in How to Write Better Requirements with Claude (Stop Letting AI Assume). That post produces the input this post consumes. If you’re going to try this workflow, start there.

Here’s the thing: the codex goal command runs on evidence, and the requirements doc is where that evidence gets defined. Every acceptance criterion becomes a checkbox the machine has to satisfy before declaring a goal complete. Write the criteria well, and you’ve written the test plan. Write them vaguely, and the build reflects that vagueness right back at you.

The leverage point from the autologin post still holds — the autonomy /goal provides downstream is paid for upfront, in the spec. Here the spec is bigger, so the downstream autonomy stretches wider too.

.

.

.

Meet wp-requirements-to-goals — The Skill That Decomposes

The autologin post introduced a skill that turns a vague paragraph into one goal trio. One input, one output.

This post’s counterpart is wp-requirements-to-goals.

Same family, different scale. It takes a structured requirements doc and produces an entire project — a goals plan, a root scaffold, and a layered tree of goals ready to execute. When I first ran it against the bulk stock manager requirements, the decomposition it produced was almost exactly what I would have designed myself — except it took minutes instead of an afternoon of whiteboarding.

The layering follows a consistent pattern:

LayerWhat it builds
00-foundationWalking skeleton — plugin activates, settings register, one artifact renders
Per-US goalsOne goal per user story, acceptance criteria copied verbatim from requirements
Non-US feature goalsCross-cutting concerns that don’t map to a single story
Integration goalRe-verifies every prior goal + cross-cutting edge cases

Each goal carries its own GOAL.md, VERIFY.md, and PROGRESS.md — the same trio from the autologin post, repeated across the full tree. Acceptance criteria are copied verbatim from the requirements document. Never paraphrased. That’s what keeps the machine’s definition of “done” identical to yours.

The integration goal at the end re-runs every previous verification — the same QC checkpoint idea readers of Your Codex Skills Should Evolve With Your Project (Ion Viper Part 2) will recognize, now baked into the WordPress skill instead of manually authored.

And before asking any questions, the skill probes the repo. It checks for existing config files, reads the slug, namespace, WordPress version, and PHP target from whatever’s already on disk. The clarification rounds stay short because the filesystem already answered most of the questions.

(Smart enough to look before it asks — which, let’s be honest, puts it ahead of a lot of people I’ve worked with.)

Codex terminal showing the wp-requirements-to-goals skill invoked against the requirements file

.

.

.

One-Shot or Phased — and the Q&A That Sets the Plan

The skill’s first question is a mode decision: generate goals phased or one-shot?

Phased writes the plan first, pauses so you can review and edit, then generates the goal files and scaffold. Safer for a first run — because the plan decomposition is the highest-risk decision. If the skill slices the requirements poorly, every downstream goal inherits the mistake.

One-shot generates the plan, scaffold, and all goal folders in a single pass. Faster, and what I chose here. The requirements doc was clean enough that I trusted the decomposition, and I wanted to see how far the unattended pipeline could stretch.

Codex asking whether to generate goals phased or one-shot, with three options: Phased recommended, One-shot, and None of the above
Selecting One-shot option to generate all goals and scaffold in one pass

After the mode decision, the skill ran through a handful of clarification rounds. I went with the recommended option on every one — the repo probe had already answered the identity questions, so these were mostly confirming sensible defaults.

(The whole exchange felt like confirming a restaurant reservation. “Table for one? Near the window? 7 PM?” Yes, yes, yes.)

First Q&A round with scaffold questions answered using recommended defaults — project identity, WordPress baseline, goal slicing, edge-case ownership
Second Q&A round covering test seeding method, derived acceptance criteria, and integration verification policy — all answered with recommended options

Then Codex laid out its five-step generation plan and started working.

Codex updated plan showing five generation steps: Phase 1 config, scaffold, foundation goal, per-US and non-US goals, integration goal

About 19 minutes later, the scaffold was done. Ten goal folders sitting in the goals directory. A root config, a plugin bootstrap folder, a verification protocol, and the bash script to run them all. Every contract written. Nothing implemented yet.

The project was runnable.

VS Code showing the finished scaffold — 10 goal folders from 00-foundation through 09-integration in the goals directory, plus root config files, ready to run

.

.

.

The Part That’s New: One Bash Command Runs Every Goal

Here’s what changed between this post and every previous one in the series.

In every prior build, I pasted each /goal command by hand. Copy the command, swap the folder name, press enter, wait, repeat. The build was autonomous within each goal, but the handoff between goals was manual. ME, copying and pasting. Every. Single. Time.

run-goals.sh removes that last handoff.

It chains every goal in order — starts the WordPress environment, runs the first goal, and when that one completes it auto-proceeds to the next, all the way through the integration goal at the end. One trigger, then leave.

Two pre-flight steps first. Install the local WordPress tooling:

Terminal showing npm install output — 404 packages installed for wp-env

Start the local WordPress environment:

wp-env start output with WordPress dev site at localhost:8888 and test site at localhost:8889

Then the trigger:

./run-goals.sh
Running ./run-goals.sh — the script starts wp-env, then launches Goal 00-foundation with danger-full-access sandbox and never approval

A practical note on plan tiers: on a ChatGPT Pro (x5) plan, the full unattended run fits inside usage limits. On a lower plan like Plus, you’d run goals in chunks to stay within limits — and the script supports exactly that:

./run-goals.sh --from 00 --to 02   # run goals 00, 01, 02./run-goals.sh --only 03           # run a single goal

The foundation goal finished in about 14 minutes. The script committed the result and moved straight to the next goal without pausing.

Goal 00-foundation completed in 14 minutes 17 seconds, auto-proceeding to Goal 01-access-control with no human input

That auto-proceed is the whole point. The autologin post removed the per-step approvals. This one removes the per-goal handoffs. You are now outside the loop for the entire multi-goal build.

.

.

.

The Nearly-Five-Hour Black Box

The first time I left a single /goal run alone, the gap was 28 minutes. That felt long.

Nearly five hours is a different animal entirely. Ten goals. The entire implementation of a multi-feature WooCommerce plugin, start to finish, with nobody at the keyboard.

I won’t pretend the first time you let a run that long go feels comfortable. The trust window is ten times wider than the autologin post, and the stakes are proportionally bigger — more goals means more surface area for things to go wrong.

About two hours in, I opened the terminal tab. Just a glance — the kind where you tell yourself you’re checking “out of curiosity,” not because you’re nervous. Goal 05 was running. I closed the tab and made dinner.

Here’s what made the absence workable:

Each goal’s VERIFY.md defines what counts as proof. The continuation prompt refuses to declare a goal complete without mapping every acceptance criterion to evidence. Scope boundaries in each GOAL.md keep Codex from wandering into unrelated files. And the integration goal at the end — which alone took 91 minutes, about a third of the total runtime — ran a full regression sweep three times, re-verifying every prior goal’s work against the live WordPress environment.

Let me say that again. A third of the total build time was pure verification.

That regression discipline carries through the whole chain. Each goal re-checks the ones that came before it. A late goal breaking an early one would surface in that goal’s own verification pass, long before the integration sweep catches it again. The tests compound across the chain, and what you’re left with is a result you can audit from the artifacts alone.

283 minutes, 9 seconds. Ten goals completed, zero skipped.

Terminal showing 10 goals completed in 283 minutes 9 seconds with 0 skipped, followed by wp-env shutdown

.

.

.

What It Cost

I’ve been writing this series for months without ever putting a dollar figure on the autonomy.

This one does.

Before this experiment, I’d browsed CodeCanyon for bulk stock managers. The $40–60 listings had mixed reviews and half of them hadn’t been updated in a year. I wanted to know whether a clean spec and under five hours of machine time could land in the same category — so I built a bash script that totals input and output tokens across the full run and applies current GPT-5.5 API pricing.

Here’s what the 10-goal build cost:

Cost calculation output showing GPT-5.5 pricing: 10 completed goals, 4.71 hours, 208M input tokens with 206M cached, 0.43M output, Short Cost $131.40, Long Cost $254.46

How to think about that number:

Hiring a freelance WordPress developer to build a multi-feature WooCommerce admin plugin from a requirements doc would cost anywhere from $500 to several thousand dollars, depending on the complexity and the developer’s rate. Buying an existing CodeCanyon plugin and customizing it runs $30–60 for the license, plus hours of adaptation time to make it fit your exact spec.

$131 for a working, tested, multi-feature plugin built from your exact requirements — with zero hands-on coding time — lands in a genuinely interesting spot.

.

.

.

Does It Actually Work? (And the UI Taste Caveat)

Closed the terminal. Opened the browser. Tested the plugin like a regular human would.

The honest caveat first: the generated admin UI is functional but plain. GPT-5.5 builds things that work, but its visual design sense is weaker than Claude models. The admin page has the right columns, the right filters, the right controls — everything the requirements specified. The layout and styling are just… adequate. Functional without any flair.

The generated Bulk Edit Stock admin page showing a product table with search, category filter, stock status filter, and columns for product name, type, stock managed, stock quantity, and stock status — functional but visually plain

A day of CSS polish from a human — or a Claude session focused on UI — would bring it up to marketplace standard. The functionality, though, is the part the requirements controlled. And the functionality held up.

Here’s the test that matters most.

I edited stock for a simple product (set quantity to 20) and for a variable product’s “Small” variation (set quantity to 19), then hit Save Changes.

Bulk editing stock quantities — WC BES G09 Seasonal Two changed to 20, Small variation changed to 19, with Save Changes button and 2 products modified indicator

Then I opened the actual WooCommerce product edit screens to check whether the values persisted. The simple product showed 20.

WooCommerce product edit page for WC BES G09 Seasonal Two showing stock quantity of 20 persisted correctly after bulk edit, with red arrow pointing to the quantity field

The variation showed 19.

WooCommerce variation edit page for Small variation showing stock quantity of 19 persisted correctly after bulk edit, with red arrow pointing to the stock quantity field

Per-variation stock on variable products is exactly where a lazy plugin implementation falls apart — WooCommerce stores variation stock separately from the parent product, and the save path requires hitting variation-specific meta fields.

That complexity is the reason I chose this plugin as the test case. And it held up.

👉 What this series keeps landing on: /goal offloads the implementation so you can spend your time being a good tester. Hours of machine work freed me to focus entirely on verification. Opening the browser, clicking through the plugin, checking that values persisted — that’s where my time belongs now.

.

.

.

Grab the Plugin

The full project is on GitHub: wc-bulk-edit-stock. Every goal folder, the bash script, the complete Codex run history — all of it. You can walk through the entire build, goal by goal, in the commit log. (It’s one of those repos where the journey is the documentation.)

If you just want the finished plugin, the releases page has a downloadable zip. Drop it into any WooCommerce site and you’ve got yourself a working bulk stock manager.

.

.

.

Use the Skill for Your Own Plugin

Install the skill:

npx skills add nathanonn/agent-skills --skill wp-requirements-to-goals --agent codex

The repo is at github.com/nathanonn/agent-skills.

One prerequisite to know about: the verification step in each goal uses playwright-cli for browser-based tests against the running WordPress environment. If you want the full workflow — including automated verification — you’ll need it installed. The playwright-cli README covers the setup.

Decomposition, scaffolding, and goal generation — that’s what the skill handles. Execution is on the bash script. But both are only as good as the requirements doc you feed in. Vague requirements produce vague goals, and the build reflects that.

The real prerequisite — ferpetesake — is learning to write requirements well. Start with How to Write Better Requirements with Claude if you haven’t already.

.

.

.

The Bigger Picture

Five entries in this series. One pattern. An input that keeps shrinking.

The autologin post started with a paragraph and a pasted command — one feature. This one started with a requirements doc and one bash command — a complete, multi-feature product.

The skill carries the domain knowledge. /goal runs the execution loop. PROGRESS.md proves the work. What changed is the ceiling — the scope of what you can build without writing code or babysitting the build.

The human’s job has compressed to two things: writing the requirements well and verifying the result. Everything between those two — decomposition, scaffolding, implementation, testing, regression — is now machine work you can trigger and walk away from. Like leaving a slow cooker on and coming back to a finished meal. (Except the meal is a WooCommerce plugin, and the slow cooker cost $131.)

The codex goal command reaches marketplace-grade complexity here, and that’s the claim this post earns. A bulk stock manager with per-variation editing, cross-cutting validation, and a full integration sweep is the kind of plugin people actually sell. The build handled it.

The honest forward edge: the UI taste gap is real, the $131 cost is real, and “marketplace-grade functionality” still needs a human’s polish and judgment before it’s ready for paying customers. Functional code and a shippable product are different things — the gap between them is taste, branding, documentation, and support. All human work.

But the part AI is getting genuinely good at — executing a well-specified plan, unattended, across an entire multi-feature build — just took another visible step.

Your job is to get good at writing the plan.


More workflows like this — AI-assisted development with Claude Code, Codex, and the tools between them — land in The Art of Vibe Coding newsletter every week. If this one was useful, the next one probably will be too.

• 11 min read • The Art of Vibe Coding

Your Codex Skills Should Evolve With Your Project (Ion Viper Part 2)

How I Used 8 Codex /goal Runs to Build a Browser Game From Scratch ended with a line I’d been sitting on for the whole post: “Part 2 is where it gets interesting.”

Let me deliver on that.

Seven more /goal runs. ~133 minutes of autonomous Codex time. Boss fights, three enemy archetypes, a power-up system, randomized waves, and a New Game Plus loop that keeps escalating difficulty. The game that was fun for 90 seconds now has a real ending — and a reason to keep playing past it.

Here’s the thing, though.

The more important story is what had to change in the skill before any of that was possible.

webg-spec-to-goal from Part 1 could scaffold a brand-new project and generate all its goals in one pass. Powerful for a greenfield build. Useless for extending a game that already had a working codebase and dozens of passing tests.

It needed to learn a second mode.

VS Code showing SKILL.md with new/extend mode dual flow beside the existing goals folder

.

.

.

Where Part 1 Left Off

Quick recap for context:

Part 1
Goals8
Autonomous time~78 minutes
Tests39
Skillwebg-spec-to-goal — scaffold + generate all goals at once
ResultPlayable, fun for ~90 seconds

The previous post covers the full foundation — what /goal is, how goals are structured, and how each goal builds on the last. Read it here if you haven’t.

This post assumes you have.

.

.

.

You Can’t Plan Everything Upfront

The original webg-spec-to-goal skill had one mode: start from scratch.

Write a paragraph describing a game. Get a starter project, a plan, and all goals. Run them in sequence. Done.

That works for an MVP.

It breaks the moment you want to add features to an existing codebase.

The first time I tried to add features to Ion Viper after Part 1, I instinctively reached for the same skill. It took about ten seconds to realize the problem — the skill’s first step was scaffolding, which would overwrite the entire project I’d just built.

After Part 1, Ion Viper was a working project — code, tests, game systems all in place. The codex goal command had built it, and the skill had no way to continue from it.

Three concrete problems:

  1. No project awareness. The skill always scaffolded a new project. Running it again would overwrite everything — scenes, tests, config, all of it.
  2. No goal continuation. Goal numbering started at 00. There was no mechanism to pick up from 08 and continue the sequence.
  3. No awareness of existing game state. New goals needed to know what systems already existed. Without that awareness, they’d duplicate work or introduce conflicts with existing tests.

If you use autonomous coding tools long enough, every project hits this moment. The initial plan is exhausted, the codebase is substantial, and the tool needs to extend what’s there rather than start over.

The solution: teach the skill a second mode.

.

.

.

Teaching the Skill to Extend

Two changes turned webg-spec-to-goal from a one-shot scaffolder into a tool that grows with the project.

Extend mode. When the user asks to add features to an existing game, the skill switches behavior:

  • Reads the existing project to understand what’s already built
  • Skips scaffolding entirely (the project already exists)
  • Continues goal numbering from the highest existing goal
  • Appends new goals to the existing plan
  • Generates only the new goal folders

QC Checkpoint. A new goal type that always lands last in any extension sequence.

It adds no features. Instead, it validates the full gameplay loop from start to finish and catches integration bugs that individual goals might miss.

(More on this one later — it earned its own section.)

Under the hood, the skill now branches early.

It detects whether you’re starting fresh or extending, and adjusts accordingly — skipping the scaffolding step and generating only the new goals while leaving everything else intact.

Here’s what makes extend mode reliable: it reads the existing plan before generating anything. That document captures the genre, the progression so far, and what’s already been built. The skill doesn’t guess what’s already there — it reads the evidence.

Codex recognizing the existing Phaser project and entering extend mode — reading goals-plan.md, state-bridge.ts, listing existing goals

.

.

.

The Enhancement Prompt

The enhancement description was six features in plain English:

  1. Rebrand from “Raiden Shooter” to “Ion Viper”
  2. Ion Blast power-up — timed multi-projectile firing
  3. Boss fight — multi-phase, health bar, victory screen
  4. New Game Plus — difficulty escalation on restart
  5. Enemy archetypes — at least 3 types with unique behaviors
  6. Randomized wave positioning
The six-feature enhancement description typed into the Codex composer with the $webg-spec-to-goal skill

Compare that to Part 1’s four-sentence game description.

The enhancement prompt is longer because it describes specific features to add to a game that already exists. Adding to a working system demands more precision than describing one from scratch.

The skill took those six features, confirmed scope in one clarification round (“Go with your recommendations” — same as Part 1), and generated 7 new goals (08–14) in a single 14-minute invocation.

Here’s what it produced:

#GoalWhat it adds
08Rebrand to Ion ViperGame identity and metadata
09Ion Blast Power-UpTimed multi-projectile pickup
10Enemy ArchetypesDrifter, shooter, charger — three distinct behaviors
11Randomized WavesBalanced random spawn positions and timing
12Boss FightMulti-phase boss, health bar, victory screen
13New Game PlusDifficulty loop, multipliers, clean restart
14QC CheckpointFull-loop validation and integration testing
Codex output showing all 7 new goal folders generated, goals-plan.md updated, ~14 minutes

.

.

.

Goals 08–13: The Enhancement Assembly Line

Same pattern as Part 1.

Paste the /goal command, swap the folder name, press enter, walk away.

Goal 08 — Rebrand to Ion Viper (7m 15s, 42 tests). A metadata pass — updated the game title, browser tab, and package info. Lightweight by design. Confirms the codebase is stable before real feature work begins.

Goal 09 — Ion Blast Power-Up (19m 36s, 47 tests). The first real feature addition. A timed pickup grants multi-projectile firing for a limited window. At 19 minutes, this was the longest non-QC goal — adding a brand-new game system on top of an existing one takes more work than extending a familiar pattern.

Goal 09 completion — Ion Blast implemented, 47 tests passing, 19m 36s

Goal 10 — Enemy Archetypes (14m 30s, 53 tests). Three enemy types replaced the single drone from Part 1. Basic drifters float downward predictably. Shooters fire projectiles back at the player. Chargers telegraph with a flash, then rush. Each type has its own health, speed, score value, and behavior — and the system is designed so adding more types later is a config change, not a rewrite.

Goal 11 — Randomized Waves (15m 30s, 57 tests). Spawn positions, timing, and lane spacing are now randomized within fair bounds. Players can’t memorize patterns anymore. The randomization layer plugs into the existing wave and spawning systems without replacing them.

Goal 12 — Boss Fight (13m 57s, 62 tests). After all waves clear, a multi-phase boss spawns with a visible health bar. Three attack phases, each with different patterns. Defeat it and you reach the victory screen. The game finally has a real ending.

Goal 12 completion — boss fight implemented, VictoryScene created, 62 tests passing, 13m 57s

Goal 13 — New Game Plus (16m 58s, 65 tests). The victory screen now offers a restart into a harder loop. Enemies get faster, tougher, and the boss scales up — all through multipliers that layer on top of the base difficulty. Loop 2 is harder than loop 1. Loop 3 escalates further.

The test count across the six goals tells the story: 42 → 47 → 53 → 57 → 62 → 65. Each goal adds its own automated tests and runs every previous test too. By Goal 13, every system from foundation to New Game Plus is covered.

.

.

.

Goal 14: The QC Checkpoint — Where the Bugs Were Hiding

Stay with me on this one, because it changed how I think about autonomous goal chains.

The QC checkpoint is a different kind of goal.

Goal 14 adds no mechanics, no art, no content.

It runs the full gameplay loop — boot, menu, play, waves, boss, victory, New Game Plus, restart, lose, game over, restart — and checks that everything reports correctly through every transition.

I expected Goal 14 to be a formality. A quick pass, green tests, done in five minutes. Instead it ran for 31 minutes and surfaced four bugs I hadn’t noticed during any of the previous six goal runs.

~31 minutes.

The longest goal in the entire project, longer than Part 1’s 26-minute polish goal.

The /goal command for the QC checkpoint — same paste-and-go format as every other goal

What the QC checkpoint caught and fixed:

  • Stale game-over state. Game data wasn’t clearing properly on restart. A player who died and restarted carried ghost data from the previous run.
  • Ion Blast firing edge case. The SPACE key needed hardening to prevent firing during pickup collection — a timing conflict between the weapon system and the power-up system.
  • Wave projectile cleanup. Enemy projectiles from cleared waves weren’t cleaning up on scene transition. They accumulated silently across wave boundaries.
  • Final wave timing. Timing adjustments were needed to ensure wave completion triggers the boss reliably instead of leaving the player in a dead state.

Every one of these is an integration bug. Each individual goal passed its own tests. But when the full loop ran — menu to waves to boss to victory to restart to game over to restart again — transitions between systems exposed assumptions that no single goal’s tests would catch.

👉 The QC checkpoint tests the seams between goals.

67 tests at the end. All passing. Including a new end-to-end test that exercises both the full win path and the full loss path.

When seven autonomous goal runs build on each other without human review between them, compound integration risk is real. Catching it automatically — before manual playtesting — makes the entire chain trustworthy.

Goal 14 completion — regressions found and fixed, 67 tests passing, integration spec added, ~31 minutes

.

.

.

Does It Actually Work?

Same instinct as every post in the series: close the terminal and play it.

Part 1’s version was fun for 90 seconds.

Part 2’s version kept me playing for longer than I’d admit.

Open the browser. Menu screen, now branded “Ion Viper.” Press SPACE.

Enemies descend — but they’re not uniform anymore. Drifters float down predictably while shooters fire back. Chargers telegraph with a flash, then rush straight at you. Spawn positions are randomized, so every playthrough arranges differently.

A glowing pickup drops. Collect it — Ion Blast activates. Your ship fires a spread of projectiles for a limited window. Clear the waves fast.

And then the boss arrives. Health bar at the top of the screen. Three attack phases. Dodge patterns change as the boss takes damage. Beat it — victory screen. “Continue to New Game Plus.” Accept.

Loop 2 starts.

Enemies are faster and tougher. The boss has more health. Die — game over, final score, restart option.

(I restarted four times before I remembered I was supposed to be writing this post.)

What Part 2 added: Ion Blast power-up, 3 enemy archetypes, randomized wave positioning, boss fight with phases, victory screen, New Game Plus difficulty loops.

What’s still absent: multiple power-up types, more than one boss, mobile controls, persistent leaderboards.

The game is playable.

It has a real ending, replayability through difficulty escalation, and it delivers what the enhancement prompt asked for.

Play it yourself: https://stunning-paprenjak-6fc3dd.netlify.app/

.

.

.

The Numbers

Part 2 build summary:

  • Skill invocation: 1 (extend mode — generated 7 new goals)
  • Goal runs: 7 (Goals 08–14)
  • Total autonomous time: ~133 minutes (~14 min skill + ~119 min goals)
  • Total human input: one enhancement paragraph + “Go with your recommendations” + 7 /goal pastes
  • Tests: 67 (up from 39 at end of Part 1)
  • Source files: 28 (up from 18)
  • Lines of game code: ~3,265 (up from 1,463)
  • Assets: 19 files (images + audio + data)

Combined totals (Part 1 + Part 2):

Part 1Part 2Combined
Goals8715
Autonomous time~78 min~133 min~211 min
Tests396767 (cumulative)
Lines of code1,4633,2653,265 (cumulative)

Series comparison (all four posts):

PostDomainGoalsAutonomous timeTests
1WordPress plugin128 minbrowser checks
2CLI tool280 min35
3Browser game (Part 1)878 min39
4Browser game (Part 2)7133 min67

Worth noting: Part 2’s per-goal average (~17 minutes) is higher than Part 1’s (~10 minutes). Feature goals on an existing codebase are more complex than greenfield goals — more existing code to understand, more systems to coordinate, more tests to run. The codex goal command does the same thing in both cases, but the surrounding context is heavier.

.

.

.

Skills Are Living Documents

Three posts ago, wp-spec-to-goal generated one goal. Two posts ago, cli-spec-to-goal detected that a project needed splitting and generated goals one at a time. One post ago, webg-spec-to-goal generated all goals at once from a single paragraph.

This post: the same skill learned a second mode.

The pattern across the series is consistent.

Each skill starts simple and gets smarter based on what the project demands. Domain knowledge accumulates in the skill — in its instructions, its templates, and its logic for deciding when to scaffold versus when to extend.

What webg-spec-to-goal knows now that Part 1’s version didn’t:

  • How to read an existing project’s state before generating goals
  • How to continue goal numbering from an existing sequence
  • How to inherit existing game state without duplicating it
  • That a QC checkpoint at the end of a long chain catches integration bugs that individual goals miss

None of this was planned in advance.

The skill evolved because the project demanded it.

Here’s what changed in how I think about this: the investment in the skill compounds. Build webg-spec-to-goal once for Part 1, extend it for Part 2, reuse the extend mode and QC checkpoint for the next game after that. The codex goal command executes what the skill produces — and the skill keeps getting better at producing the right thing.

The skill is open source at github.com/nathanonn/agent-skills. The game repo is at github.com/nathanonn/ion-viper. Clone it, run npm install && npm run dev, and play the version that 15 autonomous goal runs built.

What’s next: skills for other domains.

The three sibling skills (wp-spec-to-goal, cli-spec-to-goal, webg-spec-to-goal) share the same structural patterns but each one carries domain knowledge the others lack.

The question is where that pattern goes next.

• 10 min read • The Art of Vibe Coding

How I Used 8 Codex /goal Runs to Build a Browser Game From Scratch

One paragraph describing a game idea.

Eight /goal commands. Seventy-eight minutes of autonomous Codex time.

A playable vertical shooter in the browser.

The WordPress plugin post was one goal, 28 minutes. The CLI tool post was two goals, 80 minutes. This time: eight goals, ~78 minutes of autonomous Codex time, and the output is something you can play with a keyboard.

Here’s the paragraph that started it:

Build me a Raiden-type vertical shooter web game. The player ship is at the bottom of the screen, enemies come down from the top, and the player shoots bullets upward to destroy them. Standard 800×600 resolution. Use pixel art style.

That paragraph took three tries.

My first version was “make me a fun shooter game” — too vague for a skill that needs to generate 8 goals from it. The final version named the genre, the perspective, the resolution, and the art style.

Four sentences. Five minutes of thinking to save 78 minutes of supervision. What came out the other end is Ion Viper — a Phaser 3 game with player movement, shooting, pooled enemies, wave progression, scoring, health, a HUD, pixel art, particles, screen shake, and sound effects.

The honest caveat, same as always: the codex goal command produced the code, but the spec produced the outcome. This time the spec wasn’t a single goal trio — it was eight of them, generated all at once by a skill that understands how games decompose.

If you’re new to the series, the WordPress plugin post covers what /goal is, the goal trio (GOAL.md, VERIFY.md, PROGRESS.md), and the continuation prompt.

This post assumes you’ve read at least one of the previous two.

VS Code file explorer showing a clean repo with only .codex/skills containing webg-spec-to-goal and playwright-cli folders, beside a Codex 0.138.0 terminal session with gpt-5.5 xhigh model pointed at ~/Dev/raiden-shooter
Codex terminal showing the $webg-spec-to-goal skill invocation with the plain-English game description typed into the composer — gpt-5.5 xhigh model, 0% context used

.

.

.

Meet webg-spec-to-goal — The Skill That Generates Every Goal at Once

The WordPress plugin post introduced wp-spec-to-goal, a Codex Agent skill that turns a paragraph into the goal trio. That skill produced one goal.

The CLI tool post introduced cli-spec-to-goal, which detected the project needed splitting and generated one goal at a time — with a plan for the rest.

webg-spec-to-goal generates ALL goals in a single pass. All three skills from this series are open source at github.com/nathanonn/agent-skills.

Why?

Because games have a predictable build order that the skill can exploit:

#Goal LayerWhat it builds
00FoundationVerify scaffold boots, state bridge works
01Core MechanicThe thing that makes this game this game (shooting)
02ContentOpposition (enemies)
03–04Progression + SystemsScoring, health, HUD
05–06DepthWaves, difficulty
07PolishArt, sound, particles, juice

The skill inspects the game description, auto-detects the genre (shoot-em-up, card game, platformer, tower defense, puzzle), tailors the goal decomposition to that genre, and writes everything — the scaffold, the plan, and all 8 goal trios.

One concept needs explanation: the state bridge.

Every goal adds typed fields to window.__GAME_STATE__, and Playwright tests read that object to verify game behavior without screenshot pixel comparison. Goal 00 adds scene and ready. Goal 01 adds playerPosition and playerAlive. By Goal 06, the state bridge carries 13 fields. Verification tests assert against these fields — meaning an automated test can confirm “the player is alive and at position (400, 500)” without trying to parse pixels from a rendered canvas.

That’s what makes automated testing possible for a visual, interactive medium.

From the prompt, the skill detected “shoot-em-up” and proposed defaults:

  • name/slug (raiden-shooter, matching the repo name),
  • resolution (800×600),
  • mechanics (single weapon, no boss, no power-ups for a tight MVP),
  • and art style (pixel art with a later polish goal for audio).

I confirmed with “Go with your recommendations.”

Phaser domain knowledge also lands in AGENTS.md — scene lifecycle, object pooling, delta-time movement, state initialization in init() instead of constructors — so Codex has it available during every /goal run.

Codex output showing the webg-spec-to-goal skill running pwd, rg --files, and ls -la to explore the empty repo before asking any scoping questions
Codex presenting 5 scoping decisions with recommended defaults — name/slug, genre detection (shoot-em-up), scope (player ship, bullets, enemies, health, score), mechanics (single weapon, no boss, no power-ups), and audio (include in later polish goal) — with the user replying "Go with your recommendations"

.

.

.

The Scaffold and the Plan

The skill writes two things.

The scaffold is a runnable Phaser 3 project — TypeScript, Vite, Playwright, scene shells, state bridge — that boots at localhost:8080 and passes 3 tests out of the box.

The plan — goals-plan.md — maps all 8 goals in sequence with dependencies, acceptance criteria, and the state bridge growth table.

Here’s what the skill produced — and what each goal actually took:

#GoalTimeTests
00Foundation7m 40s3
01Player Ship6m 17s9
02Player Weapons6m 06s14
03Enemies8m 57s19
04Scoring & Health8m 54s24
05HUD6m 22s29
06Wave System7m 16s34
07Polish26m 32s39

Those are the real numbers from the actual run.

The test count column tells a story on its own. Each goal adds its own Playwright tests AND runs all previous ones. By Goal 06, the 34-test regression suite covers every system built so far. The “fields accumulate, never remove” rule in the state bridge contract is what keeps those regressions honest.

Codex output showing the skill reading scaffold and goal templates, confirming the Phaser 3 + Vite + Arcade Physics + state bridge + Playwright scaffold, and beginning to write files
Split view — left: VS Code file explorer showing the full scaffolded structure with goals/00-foundation through 07-polish, src/, tests/, public/. Right: Codex completion summary listing all created files, verification results (npm install, npm run typecheck passed, npm test 3/3 boot tests passed), and the dev server running at http://127.0.0.1:8080

.

.

.

Goal 00: Foundation — The Sanity Check

The /goal command is identical in shape to the WP and CLI versions:

/goal Complete goals/00-foundation/GOAL.md. Use goals/00-foundation/VERIFY.mdas the verification contract. Update goals/00-foundation/PROGRESS.md continuously.Treat uncertainty as incomplete.

Paste. Press enter. Walk away.

Codex terminal showing the /goal command for 00-foundation pasted into the composer, ready to execute

Codex customized the constants, wired the menu-to-game transition, set up the state bridge base fields, and wrote Playwright tests covering canvas size, console errors, and scene transitions.

7 minutes and 40 seconds. 3 tests passing. Menu screen rendering.

A lightweight sanity check — confirm the scaffold actually works before building on top of it. Every game starts here.

Goal 00 completion summary showing changed files (constants.ts, MenuScene.ts, helpers.ts, boot.spec.ts, PROGRESS.md), verification results (npx tsc --noEmit, Playwright tests, npm test), screenshot artifacts (menu.png, game-scene.png), and final time of 7m 40s

.

.

.

Goals 01–06: The Assembly Line

Six goals, each one building on the last, each one autonomous, each one inheriting the full codebase state from the run before it.

Goal 01 — Player Ship (6m 17s): Player sprite at bottom-center, WASD and arrow keys, clamped to bounds. 9 tests.

Goal 02 — Player Weapons (6m 06s): SPACE fires pooled bullets upward with fire-rate limiting. 14 tests.

Goal 03 — Enemies (8m 57s): Enemies spawn from the top, move downward, and are destroyed by bullet overlap. 19 tests. This is the moment the game becomes a game — something that absorbs your shots and shoots back.

Goal 03 completion showing pooled enemies, timed spawning, downward movement, bullet-enemy overlap destruction, offscreen recycling, enemy state bridge reporting — 19/19 tests passed, including full regression, in 8m 57s

Goal 04 — Scoring & Health (8m 54s): Score on kill, health on contact, invulnerability frames, game over on death. 24 tests.

Goal 05 — HUD (6m 22s): Parallel HUD scene showing score, health, and wave number without blocking gameplay input. 29 tests.

Goal 06 — Wave System (7m 16s): Data-driven waves replace endless spawns. Difficulty escalates. Clearing the final wave triggers a win state. 34 tests.

Goal 06 completion showing data-driven WaveSystem.ts, wave configuration in waves.ts, win-state implementation, 5 new tests plus full 34-test regression — all passed. Screenshots captured. 7m 16s

Here’s what makes the assembly line work:

Each codex goal command run starts by reading the entire codebase that previous goals built. Codex understands the existing scene structure, the existing test patterns, the existing state bridge fields. Goal 06 extends what Goals 00 through 05 established — it doesn’t start from scratch.

Somewhere around Goal 05…

Pasting the /goal command stopped feeling like an experiment and started feeling like filling out a form. Copy the command, swap the folder name, press enter. The novelty was gone by the HUD goal. That’s the point — when the eighth paste feels boring, the pattern has landed.

And the test regression discipline kept compounding.

Goal 06 runs 34 tests — 5 new ones for the wave system plus all 29 inherited from previous goals.

Nothing broke.

That’s the state bridge contract at work: fields accumulate, never get removed, and every prior test still finds what it expects.

.

.

.

Goal 07: Polish — Where the Game Comes Alive

The final goal is the longest — 26 minutes and 32 seconds — and the most visually dramatic. Placeholder rectangles become pixel art. Silence becomes sound.

Codex terminal showing the /goal command for 07-polish pasted into the composer

What Codex did in this goal:

  • Generated pixel art assets using $imagegen — player ship, enemy drone, bullets, explosion particles, space background, parallax stars — all following the magenta chromakey pipeline documented in AGENTS.md.
  • Created sound effects (fire, hit, explosion, player damage) and background music.
  • Built a FeedbackSystem: particle emitters on enemy destruction, camera shake on player damage.
  • Added parallax scrolling background with a tiling star layer.
  • Polished MenuScene and GameOverScene presentation.
  • Ran all 39 tests — 5 new polish tests plus the full 34-test regression. All green.

One honest limitation Codex flagged: it can verify that audio assets load and trigger correctly, but whether they actually sound good is a human judgment.

26 minutes and 32 seconds. More than three times the average of the other seven goals.

Polish is where the token budget earns its keep — art generation, audio integration, particle tuning, and full regression across the entire test suite.

Goal 07 completion showing pixel art assets generated, WAV sound and music integrated, BootScene loading, parallax stars, particles, camera shake, FeedbackSystem wiring, state bridge fields unchanged, 39/39 tests passed including full regression, with the audio balance caveat noted. 26m 32s

.

.

.

Does It Actually Work?

Same instinct as the previous two posts: close the terminal and test it like a real player.

The first time I opened localhost:8080 after Goal 07 finished, I expected colored rectangles with sound effects layered on top. Pixel art loaded instead — a ship, a background with parallax stars, enemies with actual sprites. The gap between what I typed into the composer and what appeared in the browser was wider than any of the previous posts.

Open the browser. Menu screen with the game title. Press SPACE.

Player ship at the bottom, pixel art sprites, parallax background scrolling. Enemies descend in waves. Bullets fire upward with sound effects. Enemies explode with particles and a satisfying pop. Get hit — screen shakes, health drops, the ship flashes with invulnerability frames. Clear all waves — win state. Die — game over screen with the final score and a prompt to restart.

Here’s the gameplay:

What’s there: movement, shooting, enemies, waves, scoring, health, HUD, pixel art, sound, music, particles, screen shake. All built by Codex across 8 autonomous runs.

What’s not there: power-ups, boss fights, multiple enemy types beyond the basic drone, weapon upgrades, mobile controls.

The game is playable.

It’s fun for about 90 seconds.

It delivers exactly what the spec asked for.

(You’ll notice some screenshots show “Raiden Shooter” in file paths and title screens — that was the working name during development. The published game is Ion Viper.)

.

.

.

The Numbers

Concrete summary of the build:

  • Skill invocation: 1 (generated all 8 goals at once)
  • Goal runs: 8
  • Total autonomous time: ~78 minutes across all goals
  • Total human input: one paragraph + “Go with your recommendations”
  • Source files: 18 TypeScript files across scenes, objects, systems, and configs
  • Test files: 8 Playwright spec files
  • Tests: 39 (all passing)
  • Assets: 9 image files, 5 audio files
  • Lines of game code: 1,463

Series comparison:

PostDomainGoalsAutonomous timeTests
1WordPress plugin128 minbrowser checks
2CLI tool280 min35
3Browser game878 min39

The trend: more goals, same pattern, roughly the same time per goal (~7–10 minutes each, except polish at 26 minutes).

.

.

.

What’s Left (And Why That’s the Point)

The game is complete as specified. All 8 goals passed. All 39 tests are green. Every verification contract is satisfied.

And it’s clearly not done.

No power-ups. No boss fights. One enemy type. No weapon upgrades. Three waves. No difficulty curve beyond wave configuration. No mobile controls. No deployment.

This is intentional.

The MVP proves the pattern works for games.

The next post (Part 2) will:

  1. Add new goal slices — power-ups, boss fights, more enemy types, weapon upgrades, difficulty tuning.
  2. Improve the webg-spec-to-goal skill based on what we learned.

The split mirrors how real game development works.

You build the core loop, validate it, then layer features on top. Goal chaining makes that natural — each new slice inherits the full state of what was built before.

If you want to play with it now, the repo is public at github.com/nathanonn/ion-viper. Clone it, run npm install && npm run dev, open localhost:8080, and shoot some drones.

Part 2 is where it gets interesting.

.

.

.

The Bigger Picture

Three posts, three domains, one pattern.

The codex goal command works the same way whether you’re building a WordPress plugin, a CLI tool, or a browser game. Skill generates spec, /goal executes spec, PROGRESS.md proves it.

The skill is the variable.

wp-spec-to-goal knows WordPress hooks and wp-env. cli-spec-to-goal knows Commander.js and exit codes. webg-spec-to-goal knows Phaser scenes, object pooling, and the state bridge pattern. Domain knowledge lives in the skill. The execution loop stays the same.

What scales: each new skill is a one-time investment.

Build webg-spec-to-goal once, use it for every Phaser game from now on. Five minutes turning a paragraph into 8 goals — the same investment whether you’re building your first game or your tenth.

What changed from March — when the Reddit summarizer needed six supervised steps — to now, when the same kind of project needs one skill invocation and a paste.

Your job is still writing the plan. The plan just got more structured.