auto-geo fix
A built-in CLI for generating a GEO-optimized rewrite of any public webpage. Where auto-geo doctor tells you what’s wrong with a page, auto-geo fix produces a ResourcePublishPayload that would pass all 8 doctor checks.
auto-geo fix https://www.example.com/some-blog-post --out ./fixed.json
The output is a JSON file that conforms to the canonical resourcePublishSchema. Publish it through your own pipeline and render at /<basepath>/<slug>; the structured fields map directly onto Article, BreadcrumbList, and FAQPage JSON-LD.
What it does
The fix pipeline orchestrates five steps:
- Fetch + parse the source URL via the same
cli/fetch.tsthe doctor uses. - Audit the parsed page with the existing doctor heuristics (
cli/checks.ts) for the “before” report. - Generate a rewrite by calling the configured LLM provider via the Vercel AI SDK (
generateObjectagainstresourcePublishSchemaas the typed-output target). The prompt embeds the GEO SOP as a system prompt and the doctor report + source-page text as the user prompt. - Validate the LLM output against
resourcePublishSchema. If parsing fails, the Zod issues are fed back as a follow-up user message and the call is retried (up to--max-retries, default 2). - Write the validated payload to
--out(default./fixed.json).
Install
auto-geo fix ships with the auto-geo package (npm i -g auto-geo) — no separate install. The AI SDK (ai, @ai-sdk/openai, @ai-sdk/anthropic) is installed alongside as a runtime dependency, so a fresh install works without extra setup. Node >=18.17 required.
# One-shot
auto-geo fix https://example.com/page --out ./fixed.json
# Installed locally
pnpm add auto-geo
pnpm exec auto-geo fix https://example.com/page --out ./fixed.json
Usage
auto-geo fix <url> [flags]
Flags:
| Flag | Default | What it does |
|---|---|---|
--out <path> | ./fixed.json | Output file path for the generated payload JSON. |
--provider <p> | openai | openai or anthropic. |
--model <name> | gpt-4o-mini | Provider model. Pass any model string the provider accepts. |
--max-retries N | 2 | Self-correction retries when the LLM output fails resourcePublishSchema.safeParse. |
--dry-run | off | Fetch + audit + estimate cost. Skip the LLM call. Useful for cost-estimation in CI. |
--json | off | Emit machine-readable JSON instead of the human report. |
--basepath <path> | /resources | Publish base path for the URL preview line. |
--author-name <s> | — | Author name (propagated into payload.author.name). |
--author-jobtitle <s> | — | Author job title. |
--author-bio <s> | — | Author bio (must be ≥20 chars to satisfy the schema). |
--author-linkedin <u> | — | Author LinkedIn URL. Drives Person.sameAs in the auto-emitted JSON-LD. |
Environment variables
The fix CLI authenticates to the chosen provider via the standard environment variable:
OPENAI_API_KEY— required when--provider openai.ANTHROPIC_API_KEY— required when--provider anthropic.
Missing keys produce a clear error before any network is issued; the dry-run flag does not require a key.
Example session
$ auto-geo fix https://www.example.com/some-blog-post \
--out ./fixed.json \
--provider openai \
--model gpt-4o-mini
auto-geo fix — generate a GEO-optimized rewrite
url: https://www.example.com/some-blog-post
fetched: 1,247 words
Audit (before):
✗ TL;DR present
✗ Question-format H2 headings (2 of 6)
✗ Article JSON-LD present
✗ FAQPage JSON-LD present
✓ Entity density (12.3/1k)
✗ Image cadence (0/page)
✓ Answer-first first paragraph
✗ Self-link detected
Score (before): 3 / 8
Generating rewrite via openai gpt-4o-mini...
Audit (projected for rewrite):
✓ TL;DR present (52 words)
✓ Question-format H2 headings (6 of 6)
✓ Article JSON-LD (auto-emitted by ResourceArticle renderer)
✓ FAQPage JSON-LD (auto-emitted)
✓ Entity density (18.1/1k)
✓ Image cadence (n/a — payload doesn't include images yet; add via the publish endpoint)
✓ Answer-first first paragraph
✓ No self-link
Score (projected): 8 / 8 — strong GEO posture
→ ./fixed.json (validated by resourcePublishSchema)
Total: ~$0.04 estimated · 12s elapsed
Next steps:
1. Review ./fixed.json (especially TL;DR + FAQ — the citation-target chunks)
2. Publish to your endpoint
3. Re-audit: auto-geo doctor https://www.example.com/<new-slug>
How the prompt is constructed
The system prompt is a deliberately compact digest of the GEO SOP:
- The seven-block page architecture in order.
- Every hard rule the schema enforces (word counts, slug regex, banned promotional language, etc.).
- The empirical quality drivers (question-format H2s, entity density ≥15, answer-first ledes, self-contained answer capsules).
- A note that JSON-LD is auto-emitted by the renderer from the typed payload — the LLM should not generate it.
The user prompt adds:
- The source URL.
- A digested doctor report with pass/fail for each of the 8 checks and the imperative fix suggestion for the failing ones.
- The source page text, truncated to ~24,000 characters with a head/tail split so the LLM sees both the opening and the closing material. Public web pages can be hundreds of KB; without a cap, the context window blows up before generation begins.
- An optional author block built from the
--author-*flags. The schema requiresauthor.{name, jobTitle, bio}; if any are missing, the LLM is told to invent reasonable defaults that satisfy the schema.
Self-correction loop
The LLM occasionally emits a payload that satisfies generateObject’s in-transit type-check but fails the stricter resourcePublishSchema.safeParse (typically on word-count refinements or the slug regex). When this happens:
- The Zod issues are formatted as a bulleted list with the field path.
- The original user prompt is concatenated with a follow-up instruction: “Your previous output failed schema validation. Fix every issue below and emit a NEW payload that satisfies the schema.”
- The call is retried.
After --max-retries failed attempts (default 2, so up to 3 total calls), the CLI throws with the final Zod issue list.
Cost model
The “Total: ~$0.04 estimated” line is a rough USD estimate derived from a small in-process price table covering the common OpenAI and Anthropic models. The model assumes:
- ~4 characters per token for input (a standard heuristic).
- ~2,000 output tokens for a typical full payload.
The estimate is for budgeting; the actual cost is whatever your provider bills. The dry-run mode prints the estimate without issuing a request.
Auto-emitted JSON-LD
Two of the doctor checks — article-jsonld and faqpage-jsonld — are renderer concerns: the structured fields on the payload map directly onto Article, BreadcrumbList, FAQPage, Person, and ImageObject JSON-LD blocks, so whatever renders the payload should emit them mechanically. The LLM does not need to generate any JSON-LD itself; the projected-audit lines for those two checks call this out explicitly.
Programmatic API
The orchestrator is also importable for callers that want to embed fix in their own tooling:
import { runFix } from "auto-geo/cli/fix";
const outcome = await runFix({
url: "https://example.com/page",
out: "./fixed.json",
provider: "openai",
model: "gpt-4o-mini",
maxRetries: 2,
dryRun: false,
json: false,
basePath: "/resources",
});
console.log(outcome.before.scorePct, "→", outcome.after.scorePct);
console.log(outcome.payload);
All network, LLM, and filesystem dependencies are injectable for testing — see tests/fix.test.ts for the patterns.
Troubleshooting
- “OPENAI_API_KEY is not set” — Export the key (
export OPENAI_API_KEY=sk-…) before invoking. The dry-run flag does not require a key. - “Generated payload failed final schema validation” — The LLM’s output failed
resourcePublishSchema.safeParseeven after--max-retries. Bump--max-retries 4or switch to a more capable model (gpt-4o,claude-sonnet-4-5). - Sluggish on a large page — The source text is capped at ~24,000 characters by default; very long pages get a head/tail summarized window. The LLM call itself is what takes the bulk of the time.
- Wants to add images to the payload — Image blocks are not synthesized by the fix CLI. Edit the JSON before publishing, or POST it as-is and add images via a follow-up publish (the publish endpoint is idempotent on slug).