seodraft
Local-first SEO/AEO content engine: installs an agent skill + deterministic scripts into your AI coding harness.
Package details
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 feedsdateModifiedin 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