Journal

Real SEO case studies and field notes from building in public.

· 8 min read

How to Present an SEO Audit to a Client Without Jargon

The audit that wins the next invoice is not the thickest one — it is the one the client can read. This post is a working method for turning raw SEO audit output (priority matrices, authority scores, intent splits, cluster dumps) into a client presentation with zero jargon: a verdict-first structure, a translation pass with real before/after examples from an actual audit run, a 30-second verdict paragraph formula, and the exact CLI commands that produce a white-labeled HTML report you can put your agency's name on. Real output from a 12-step audit run on a live site is shown throughout — including the parts that made the site look bad, and why showing those builds the trust that a glossy PDF destroys.

Read more →
· 4 min read

Turn Your CLI Into Agent Skills: What I Learned Shipping 9 Installable Skills

A CLI with 22 tools and a README is still invisible to agents — agents don't read docs, they read SKILL.md files that tell them when to fire, what to run, and how to read the output. I restructured ZensInk into 9 installable job-skills (npx skills add), and the process surfaced three things nobody tells you: the skills CLI stops at a root SKILL.md and never scans your nested skills, one malformed frontmatter line silently drops a skill from the listing, and metadata.source turns every installed copy into a citation surface that carries your URL into other people's agent sessions. Includes the real install session, the job-skill grouping logic, and why this matters for AI-search visibility when your DR is too low for classic SEO to move.

Read more →
· 5 min read

Query Fan-Out Coverage Audit: Which Branches of AI Search Does Your Site Actually Answer?

Fan-out planning tells you what to write before you write it. This post is the other half: auditing a site that already exists. Google AI Mode expands one query into 8-12 sub-queries — pricing branches, audience branches, comparison branches — and cites the pages that answer them. A coverage audit asks one question: when that fan hits your site, how many branches does it find? Method is three steps and zero paid tools: generate the sub-query tree with geo_fanout, pull your own sitemap, match tokens, then hand-review. I ran it on this site: 55 sub-queries against 190 pages, 18% raw coverage, 15% after removing false positives. The interesting output isn't the score — it's the miss list, filtered through an intent-fit pass that kills branches your product strategy has no business chasing.

Read more →
· 6 min read

Python Rank Tracker: Track Google Positions in SQLite Without a $99/mo Tool

Rank tracking subscriptions start around $99/month and go up from there, but the mechanical core of the product is small: fetch a SERP, find your domain, store the position, diff it against last time. This post walks through rank_tracker, the SQLite-backed rank tracking CLI in the zens-ink package — add / check / status / trend / history, one Serper.dev API call per keyword per check, storage in a local SQLite file, pure Python stdlib. Real data from two months of tracking zens.ink: the brand term slipped from #1 to #2 behind the product's own storefront page, a same-minute SERP variance incident that showed one snapshot 'not in top 20' and the next three at #2, and a cross-check against Google Search Console's averaged positions that explains why GSC says position 7 while a live anonymous SERP says not-in-top-20. With cost math for weekly checks on a 17-keyword set and the honest limits of point-in-time rank measurement.

Read more →
· 7 min read

Can AI Agents Read Your Repo? A 10-Check Audit With Real Scores From 3 Repositories

Your repository has two audiences now: humans and AI agents. When a developer asks ChatGPT, Claude, or Copilot 'what does this repo do' or 'recommend a tool for X', the answer is shaped entirely by what the agent can digest — README, llms.txt, AGENTS.md, entry points, code structure. I built a 10-check audit that scores how well a repo survives that digestion, then ran it against three real projects: an agent-optimized CLI package (91/100, grade A), a typical internal dashboard (36/100, grade F), and a content site with an MCP server but no README (27/100, grade F). The pattern in the failures surprised me: both F repos scored perfect on code readability. Agent readability is not a code quality problem — it's a surface problem. This post covers the full checklist, the scoring weights, the P0/P1/P2 fix order, and how to run the audit yourself.

Read more →
· 5 min read

Check Any Domain's Ahrefs DR for Free in Python (No Subscription, Pure Stdlib)

Domain Rating is the number every link-building conversation starts with, and the tool to check it costs hundreds of dollars a month — unless you know about Ahrefs' free public DR API. This post walks through domain_rating, the DR lookup CLI in the zens-ink package: single domains, batch files, CSV export, and a local cache that keeps repeat runs instant. Real runs included — stripe.com at DR 95, metamask.io at DR 89, and our own portfolio pulled from cache in half a second. Plus the caveats that matter: DR is Ahrefs' metric (attribution required), cached values go stale (we caught our own site showing a two-month-old DR), and DR alone never tells you whether a link is worth chasing.

Read more →
· 8 min read

GSC Quick Wins in Python: Mine Position 5-30 Before You Chase New Keywords

The cheapest keyword research is the data you already own: Google Search Console queries where your site has proven relevance but sits at position 5-30 with almost no clicks. This post walks through search_performance, the GSC CLI in the zens-ink package — one-time OAuth setup, then queries, pages, and per-page filtering from pure Python stdlib — plus the quick-win filter (impressions >= 5, position 5-30) that turns raw rows into a ranked refresh list. Real runs on zens.ink's own GSC data: 621 impressions and 2 clicks over three months, 7 of 40 queries landing in the quick-win band, including a position-8 query with 138 page impressions converting at 2.2% CTR. Why position is 'personalized difficulty already measured', why the filter's other job is telling you what NOT to chase, and the honest limits of GSC's averaged position metric.

Read more →
· 8 min read

Search Intent Classification in Python: Read the SERP, Not the Keyword

Keyword-text intent classification fails exactly when it matters — on ambiguous modifiers like 'best' and 'tools' that can swing informational or commercial depending on the niche. This post walks through serp_intent, a Python CLI that classifies search intent from the live SERP instead: it fetches the top 10 results through Serper.dev, labels each ranking URL with one of 20 page types (path regex, domain overrides, title patterns), converts the page-type distribution into position-weighted 0-3 intent scores, detects SERP features, and cross-validates against keyword-based classification — flagging mismatches with a 'trust the SERP' verdict. Real runs on six keywords, including a caught mismatch where the keyword text said informational and Google's results said commercial, a fragmented SERP with Reddit outranking vendors, and the honest failure modes of heuristic page-type labeling.

Read more →
· 5 min read

Reddit Keyword Research in Python: Mining Autocomplete for Threads Nobody Has Answered

People type 'reddit' into Google before they type anything else — and autocomplete remembers every one of those searches. This post shows how to turn that free signal into a keyword pipeline: the zens-ink reddit_blueocean tool queries Google Autocomplete for reddit-prefixed variants of your seed keyword, scores each one for commercial intent and competition (formula included, it's three lines of arithmetic), and surfaces the queries where demand exists but no good answer ranks yet. Real output from two live runs ('seo tools' → 3 blue ocean queries, 'note taking app' → 7, all low competition). Plus the supply-side layer: when the Reddit API is reachable, the same tool scores individual posts by traffic-signal-vs-engagement gap — high traffic with <20 upvotes means Google sends readers and nobody answered. Also covered: why the tool still works when Reddit's API returns 403 (layered fallback by design), what autocomplete can and cannot tell you, and the two ways to act on a blue ocean query: build the content page, or answer the thread yourself.

Read more →
· 6 min read

SEO MCP Server in Python: Exposing CLI Tools to AI Agents with Zero Dependencies

AI agents can read your docs, but they can't run your bash pipeline — unless the tools speak MCP. This post walks through a zero-dependency stdio MCP server written in ~500 lines of pure Python stdlib that exposes 17 SEO CLI tools (keyword research, SERP difficulty, site audit, GEO fan-out...) to any MCP client: Claude Desktop, Codex, DSH, anything speaking the protocol. The architecture is deliberately boring: the server owns zero business logic, every tool call spawns the existing CLI as a subprocess, so behavior is byte-identical between terminal and agent. Real JSON-RPC session transcript, wiring configs for three clients, per-tool timeout design, and the honest limits of a minimal implementation — plus the self-referential kicker: the target keyword for this article scored kd 11.5 (Very Easy) through the same server.

Read more →
· 2 min read

Ahrefs and Backlinko Have No llms.txt: An AI Crawler Audit of the SEO Industry

Everyone sells GEO advice; almost nobody audits the layer that decides whether AI engines can read the site at all. I pointed a new zero-dependency CLI at three famous SEO sites and my own projects. Findings: ahrefs.com and backlinko.com both return 404 on llms.txt, subpath disallows get misread as full blocks by naive checks, and robots.txt allows do not survive Cloudflare's edge bot toggles. Real CLI output for every claim.

Read more →
· 5 min read

Query Fan-Out Content Planning in Python: Map AI Search's Sub-Queries Before You Write

AI search engines don't answer the query you typed — Google AI Mode and AI Overviews expand one search into 8-12 sub-queries across different intent dimensions, then synthesize. If your article only answers the head query, you're invisible for every other branch of the fan-out. This post shows a zero-dependency Python CLI that enumerates the fan-out tree for any seed keyword: 7 intent dimensions, 55 sub-queries per seed, live autocomplete validation of which branches have real demand, and a gold-mine score that surfaces high-intent low-competition branches. Real output from live runs, the honest limits of the heuristics, and why the 'serendipitous queries' section of the report is where the actual edge lives.

Read more →
· 4 min read

Free Domain Rating Checks in Python: Ahrefs' DR API Without a $99/mo Subscription

Domain Rating is the one SEO metric everyone wants and almost nobody wants to pay $99+/month for. It turns out Ahrefs runs a free public DR endpoint — you just need a free API key and a thin wrapper. This post shows the exact CLI setup, a real 8-domain batch run, and the uncomfortable example that proves DR measures links, not quality (example.com scores 94). Plus where DR belongs in your pipeline and where it actively misleads.

Read more →
· 5 min read

Serper vs Brave Search API: A Real Benchmark for SEO Scripts (2026)

Most SERP API comparisons are vendor listicles. This one is a side-by-side run: 8 keywords through Serper.dev and Brave Search API, measuring latency, top-10 overlap, and SERP features. The uncomfortable finding — Brave agrees with Google on only half the results, and diverges most on exactly the long-tail queries SEO tools target. Plus the 2026 pricing flip that killed the 'free Brave fallback' advice.

Read more →