seodraft

Local-first SEO/AEO content engine: installs an agent skill + deterministic scripts into your AI coding harness.

Packages

Package details

skillprompt

Install seodraft from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:seodraft
Package
seodraft
Version
0.2.0
Published
Aug 31, 2026
Downloads
142/mo · 142/wk
Author
chorch
License
Apache-2.0
Types
skill, prompt
Size
124.1 KB
Dependencies
0 dependencies · 0 peers
Pi manifest JSON
{
  "skills": [
    "./skill"
  ],
  "prompts": [
    "./prompts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

seodraft

An SEO/AEO content engine that lives in your repo, powered by the agent you already pay for.

It installs an agent skill plus a set of dependency-free Node scripts into the AI coding harness you already run, and turns it into a content operation: keyword research, a content calendar, article generation, and a deterministic pre-publish gate.

No SaaS. No model API keys. Your articles are Markdown files in your repo and your state is JSON files you can read in a diff.


How it works

The division of labor is the whole design:

Your agent proposes keywords, schedules, research, drafts, fix suggestions
Deterministic code decides 15 gate rules, 18 audit rules — no LLM, no network, milliseconds
You approve nothing is published or edited without you

A reviewer gets tired; a gate does not. Every question with a factual answer (is the meta description within budget, does this internal link resolve, is this term already covered by another post) is answered by a script on every article, forever. Everything that needs taste stays with you and your agent.

Requirements

  • Node.js ≥ 18
  • A Markdown/MDX content repo — Astro, Next.js, Hugo and Jekyll are detected automatically, and Markdoc/Keystatic (.mdoc) works too
  • One of: Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Pi

Install

From your content repo:

npx seodraft@latest install

install detects which harness folders exist (.claude, .agents, .cursor, .gemini, .opencode, .pi), asks you to confirm, and copies the skill in. Pass --providers=claude,cursor to skip the prompt, or --scope=global to install into ~ instead of the project. update re-copies over an existing install — run it after bumping the package.

Installing globally works too, and gives you the CLI on PATH for CI use:

npm install -g seodraft

To run from source instead:

git clone https://github.com/ch0rch/seodraft.git
cd seodraft && pnpm install
node /path/to/seodraft/bin/seodraft.mjs install   # from your content repo

Then open your agent in your content repo and run /seodraft init.

Pi

Pi loads seodraft as a native package, in place — nothing is copied into your repo and pi update keeps it current:

pi install npm:seodraft

Install it without a version: a pinned spec (npm:seodraft@0.2.0) is deliberately skipped by pi update --extensions. The package ships the skill plus a /seodraft <command> prompt template; /skill:seodraft works too.

Pick one route per machine — the package or seodraft install --providers=pi. Both at once makes pi report a seodraft name collision and load only the first one it finds. And if you already installed for Codex, pi picks the skill up for free: it reads .agents/skills/ as well.

Commands

Inside your agent:

command what it does
/seodraft init detects your framework, interviews you about the business, writes .seodraft/config.json + profile.md
/seodraft keywords builds the keyword bank — real metrics via DataForSEO, or clearly-marked estimates without it
/seodraft plan schedules stored keywords into a content calendar
/seodraft write research → outline → draft → gate, then updates state. Max 2 redrafts, then it stops
/seodraft audit deterministic audit of existing content, with fixes applied only after you approve
/seodraft status queue, keyword coverage, AEO coverage, last audit counts

The same scripts run headless for CI, no harness required:

seodraft gate content/blog/my-post.mdx --term "target keyword"   # exit 1 on failure
seodraft audit --file content/blog/my-post.mdx                   # always exit 0
seodraft state validate

The gate

gate.mjs blocks an article on any error-severity failure. It is the reason a bad draft never becomes a file you have to clean up later.

rule severity
frontmatter-parse, frontmatter-complete error
title-length, description-length error
h1-policy, heading-structure error
internal-links-min, broken-internal-link error
cannibalization error
placeholder-text error
tldr-length, faqs-min error
cannibalization-fuzzy, thin-content, faq-answer-length advisory

cannibalization is a hard stop, never a redraft: if a term is already covered by one of your posts, a second post splits your own ranking. The agent is forbidden from rephrasing around it.

AEO

Three canonical fields exist because answer engines quote structure, not prose:

  • tldr — a self-contained answer to the target term. This is the block an answer engine lifts.
  • faqs — quotable question/answer pairs.
  • updatedDate — the freshness signal. It feeds dateModified in your structured data; without it, a post you refreshed can never say so.

The same AEO rules are errors in the gate and advisories in the audit. That split is deliberate. New content has no excuse for missing fields your own schema declares. But erroring every post in a legacy archive that predates the standard produces noise, not signal, and buries the findings that matter. A missing TL;DR on a three-year-old post is an opportunity; a missing meta description was always a defect.

Configuration

.seodraft/config.json (committed) maps nine canonical fields onto whatever your repo actually calls them. Values may be dot-paths into nested frontmatter, and null means the field is unused here — which also disables its rules.

{
  "schemaVersion": 1,
  "siteUrl": "https://example.com",
  "language": "en",
  "locationCode": 2840,
  "framework": "astro",
  "contentDir": "src/content/blog",
  "extension": ".mdx",
  "frontmatter": {
    "title": "seo.seoTitle",
    "description": "seo.seoDescription",
    "date": "publishedDate",
    "updatedDate": "updatedDate",
    "image": "coverImage",
    "tags": "category",
    "draft": "draft",
    "tldr": "seo.tldr",
    "faqs": "faqs"
  },
  "bodyH1": false,
  "internalLinksMin": 2,
  "thinContentWords": 500,
  "titleMax": 60,
  "descriptionMax": 160,
  "tldrMin": 100,
  "tldrMax": 400,
  "faqsMin": 3,
  "faqAnswerMax": 500
}

State files, all in .seodraft/:

file committed holds
config.json yes the above
profile.md yes business, audiences, competitors, voice, instructions
keywords.json yes the keyword bank
calendar.json yes scheduled entries
config.local.json no DataForSEO credentials

Keyword metrics are optional

DataForSEO gives you real search volume and difficulty. Without credentials, dataforseo.mjs exits 0 with {"degraded": true, "reason": "missing-credentials"}, and the agent estimates the metrics instead, marking those entries source: "estimate". Real measurements always overwrite an estimate; an estimate never overwrites a measurement.

Credentials go in DATAFORSEO_LOGIN / DATAFORSEO_PASSWORD or in the gitignored .seodraft/config.local.json.

Contributing

Issues and pull requests are welcome. Start with CONTRIBUTING.md — it is short, and it covers the one constraint that is easy to break by accident: everything under skill/scripts/ must stay dependency-free, because those scripts run inside someone else's harness with no node_modules anywhere near them. CI enforces it mechanically.

pnpm install
pnpm test                # 81 tests, 10 suites
pnpm check:deps          # the dependency guard
pnpm smoke               # packs the tarball, installs it, runs it

There is no build step: skill/ is the product and the installer copies it. prompts/ is Pi-only: it is loaded straight out of the package and is never part of the copied skill.

fixtures/astro-blog/ is a content repo seeded with a known set of defects that the gate and audit tests assert against exactly — it is a test fixture, not sample data.

By participating you agree to the Code of Conduct. For vulnerabilities, see SECURITY.md — please do not open a public issue.

Not in v1

Google Search Console OAuth and opportunity scoring, AI-citation tracking (v1 enforces citable structure; it does not measure citations), WordPress publishing, and image generation.

License

Apache-2.0