Skip to content

Latest commit

 

History

History
164 lines (121 loc) · 9.36 KB

File metadata and controls

164 lines (121 loc) · 9.36 KB
title write
parent Commands
nav_order 3
description auto-geo write — generate publish-ready GEO resource pages from target queries.

auto-geo write

Generate publish-ready GEO resource pages from a set of target queries — the questions you want your domain to be cited for inside ChatGPT, Perplexity, Claude, Gemini, and Google AI Overviews.

auto-geo write \
  --domain https://www.shadow.inc \
  --query "what is GEO" \
  --query "GEO vs SEO" \
  --query "how to get cited by ChatGPT" \
  --out ./resources \
  --provider openai \
  --model gpt-4o

Each query produces one JSON file in <out>/<slug>.json, validated against the canonical resourcePublishSchema. Files are ready to publish through your own pipeline — CMS import, static-site build, or custom renderer.

Why this exists

auto-geo doctor audits whether a page is shaped for citation. auto-geo write produces a page that already passes the audit. It encodes the GEO SOP as a system prompt, uses the Vercel AI SDK's generateObject with a Zod schema to constrain output, re-validates with the same schema your publish endpoint enforces, and feeds Zod issues back to the model on failure for a bounded self-correction loop.

Install + auth

The CLI ships with auto-geo (npm i -g auto-geo). The AI SDK and provider packages are installed alongside as runtime dependencies, so a fresh install already has everything it needs.

auto-geo write --help

Set the API key for the provider you choose:

export OPENAI_API_KEY=sk-…     # for --provider openai (default)
# or
export ANTHROPIC_API_KEY=sk-…  # for --provider anthropic

If the relevant key is missing, the CLI exits with a clear error (exit code 2). --dry-run does not require an API key.

Flags

Flag Default What it does
--domain <url> required Publisher domain. Used to scope internal relatedGuides links.
--query <text> required, repeats Target query. One page per --query.
--queries-file <path> Newline-separated file of queries. Lines beginning # are comments.
--out <dir> ./out Output directory. Created if it doesn't exist.
--provider <id> openai LLM provider (openai | anthropic).
--model <name> provider default gpt-4o-mini for openai, claude-sonnet-4-6 for anthropic. Cheap defaults so a small run stays cheap.
--basepath <path> /resources URL path under your domain where resources are served.
--author-name <text> Shadow Research Used for the page's author.name.
--author-jobtitle <t> Used for author.jobTitle.
--author-bio <text> Shadow team bio Used for author.bio. Pass @path/to/bio.txt to read from a file.
--author-linkedin <u> Used for author.linkedinUrl.
--max-retries N 2 Self-correction retries when the LLM draft fails Zod validation.
--concurrency N 2 Parallel LLM calls. Capped at 2 by default to respect rate limits + cost predictability.
--dry-run Compute the plan + cost estimate without calling the LLM.
--json Emit a machine-readable summary on stdout.
--no-color Disable ANSI colors even on a TTY.

Exit code: 0 if every page validates and writes; 1 if any page fails after retries; 2 on bad arguments or missing env.

Slug derivation

The slug is derived from the query: lowercased, kebab-case, with question-form stopwords (what, how, is, the, a, to, by, …) stripped. Examples:

Query Slug
what is GEO geo
how does GEO work? geo-work
GEO vs SEO geo-vs-seo
how to get cited by ChatGPT get-cited-chatgpt

When multiple queries collapse to the same slug, the second and subsequent get -2, -3, … appended.

The system prompt

auto-geo write ships a single system prompt that encodes the schema's hard rejection criteria and the audit's soft heuristics. Read the canonical version at cli/llm.ts (look for SYSTEM_PROMPT). Highlights:

  • TL;DR: 40–60 words, must answer the target query.
  • Answer capsules: 40–60 words per H2, self-contained.
  • FAQ answers: 40–60 words each, 3–10 FAQ items.
  • Related guides: 4–8 entries; mix of internal (/{basepath}/<slug>) and authoritative external (Princeton GEO paper, Schema.org, Search Engine Land).
  • Key takeaways: 4–6 entries, each 10–35 words.
  • H1 title: the target query phrased as a question.
  • No banned superlatives: industry-leading, revolutionary, cutting-edge, world-class, next-generation, etc., unless attributed or inside quotes.
  • No raw HTML: **bold**, *italic*, [text](url) only.

Self-correction loop

1. LLM call (generateObject) → candidate payload
2. resourcePublishSchema.safeParse → pass or issues[]
3. If pass: write to <out>/<slug>.json, done
4. If fail and attempts < maxRetries:
   - Re-prompt with the previous draft + numbered Zod issues
   - Go to 1
5. If fail and attempts exhausted:
   - Emit a `failed` outcome with the issue list

The SDK's generateObject already enforces JSON-mode + tool-use against the Zod schema, so the model can't return malformed JSON. But the SDK adapter doesn't run our custom superRefine callbacks (word-count windows, banned-superlative regex, table-row consistency), so we re-validate defense-in-depth.

Most validation failures clear on the first retry: the model sees the exact numeric path that's wrong and corrects it.

Cost estimation

Token usage is read from the AI SDK result (usage.inputTokens, usage.outputTokens) and multiplied by per-model rates hardcoded in cli/cost.ts. Cheap-default models are quoted accurately; premium models fall back to a conservative provider default — verify against your billing dashboard.

Total: 3 pages · ~$0.18 spent · 47s elapsed

--dry-run shows the same line with estimated instead of spent based on observed per-page token counts (~4.7k tokens).

Programmatic API

import { runWrite, DEFAULT_AUTHOR } from "auto-geo/cli/write"; // not a published subpath; for internal use
import { createOpenAI } from "@ai-sdk/openai";

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const summary = await runWrite({
  domain: "https://www.shadow.inc",
  queries: ["what is GEO", "GEO vs SEO"],
  outDir: "./resources",
  provider: "openai",
  modelName: "gpt-4o-mini",
  model: openai("gpt-4o-mini"),
  basePath: "/resources",
  author: DEFAULT_AUTHOR,
  publishedAt: "2026-06-02",
  maxRetries: 2,
});

The same runWrite powers the CLI; it's a single function call so you can wire it into your own pipeline (Inngest job, cron, webhook).

Troubleshooting

"OPENAI_API_KEY is not set" — Export the env var, or switch to --provider anthropic with ANTHROPIC_API_KEY set.

"Generated payload failed schema validation after N attempts" — The model couldn't produce a schema-conformant page. Raise --max-retries, switch to a smarter model (--model gpt-4o, --model claude-opus-4-5), or rephrase the query. Inspect the issue list in the failure output.

"--provider must be 'openai' or 'anthropic'" — Only these two providers are wired in. Add a feature request to extend; the AI SDK supports many more.

Cost surprises — Always run --dry-run first on a large query batch. Tighter cost control: use --model gpt-4o-mini or --model claude-haiku, both ~10x cheaper than the flagship models.

Self-link in relatedGuides — The schema doesn't reject self-links (the audit warns instead). The system prompt instructs the model to avoid them, but if you see one, edit the generated JSON before publishing.

Re-audit after publish

Once you've published the JSON, point auto-geo doctor at the live URL to confirm the page renders with the expected citation signals:

auto-geo doctor https://www.shadow.inc/resources/geo