AI visibility
What this is for
Search used to rank pages; assistants recommend brands. When someone asks ChatGPT "which is the best X for Y", they get three to five names and pick from that list. There is no second page. If the brand is not in the list, it does not exist for that buyer.
Three facts shape everything below:
- Answers vary by model. Overlap between the domains ChatGPT and Perplexity cite is roughly 11%. Checking one assistant tells you almost nothing.
- The description matters as much as the mention. When a model names a brand, it also characterises it in one sentence the brand did not write. That sentence is the positioning buyers act on.
- Most of the signal is off-site. Around 85% of brand mentions in AI answers trace back to third-party sources, not the brand's own pages. Rewriting the homepage alone rarely moves the number.
This skill measures the current state, then produces a ranked list of fixes. It runs locally on plain files — no account, no database, no API key required.
Workflow
Work through these in order. Steps 1-5 are the measurement; 6-8 are the fix. Announce which step you are on so the user can follow along.
Everything lives in a workspace directory — create it inside the user's project (or wherever they ask), never in a temp folder they will lose.
All scripts are python3 with the standard library only. Invoke them as
python <skill>/scripts/aeo.py -w <workspace> <command>.
1. Set up the brand
python scripts/aeo.py -w ./ai-visibility init example.com --name "Example"
Then interview the user and fill in brand.json. Getting this right decides
whether the whole audit is accurate:
| Field | Why it matters |
|---|---|
domain |
The strongest mention signal. Bare domain, no www, no protocol. |
aliases |
Other names the brand goes by, including in other alphabets. A brand called in Cyrillic will be missed without this. |
keywords |
Distinctive product or service names that identify the brand even when its name is absent. |
competitors |
Named rivals. Unknown competitors are discovered automatically; listing the known ones makes share-of-voice comparable across runs. |
positioning |
One sentence: what it does, for whom, what makes it different. Used later to judge whether the model's description is right. |
If the user does not know their competitors, run step 4 first with a handful of prompts — the answers will name them.
2. Build the prompt set
This is the step most people get wrong, and it determines whether the audit is
useful. Read references/prompt-research.md before writing prompts.
The short version: buyers write ~23 words describing their situation, not 3 keywords. Brand-name prompts are easy and nearly worthless. What you want are the unbranded, situation-specific questions asked right before a decision.
Generate 15-25 prompts with the user, in their buyers' language (not
necessarily English), each tagged with category and intent. Write them to
prompts.json. Intent drives prioritisation later, so classify honestly:
high = close to buying, low = browsing.
3. Collect answers
Three routes; pick based on what the user has. B is the most trustworthy because it captures what real assistants actually serve today.
A — you answer them (no setup). Create the files, then answer each prompt
yourself using web search, writing the answer into the file below the ====
line.
python scripts/aeo.py -w ./ai-visibility run --models claude
Answer each prompt as you would for any user asking it cold: search, then give
your honest recommendation. Do this before reading brand.json, and do not
let the tracked brand influence the answer — otherwise the measurement is
worthless. Label this run claude and tell the user plainly that it is one
model's view, produced by the same assistant running the audit.
B — the user pastes real answers (recommended).
python scripts/aeo.py -w ./ai-visibility run --models chatgpt gemini perplexity claude
This creates one file per prompt × model. The user opens each assistant, asks
the prompt, and pastes the full answer under the ==== line, replacing
(no response). Tell them to paste everything including links — citations are
data. Leaving a file untouched is fine; unfilled answers are excluded from the
percentages rather than counted as misses.
C — automated via OpenRouter. One API key covers every model.
export OPENROUTER_API_KEY=sk-or-...
python scripts/aeo.py -w ./ai-visibility collect --models chatgpt gemini perplexity
Roughly $0.02-0.10 per prompt × model with the default lightweight models. Warn the user before spending their money, and quote the count: 20 prompts × 3 models = 60 calls.
4. Analyze and report
python scripts/aeo.py -w ./ai-visibility analyze
python scripts/aeo.py -w ./ai-visibility report
This scores every answer — mentioned or not, the sentence describing the brand,
position in any ranked list, sentiment, competitors, cited URLs — and writes
report.html (self-contained, openable in a browser) plus summary.md.
5. Read the results with the user
Do not just hand over the percentage. Walk through, in this order:
- Lost high-intent prompts. Each one is a buyer being sent to a competitor. This is the whole point of the exercise.
- The descriptions. Open
runs/<date>/results.jsonand read thedescriptionfield for answers where the brand appears. Compare each topositioning. A brand that sells premium but gets described as "a budget option" has a perception problem no amount of technical work will fix. - Position. Being 5th of 5 is closer to invisible than to 1st.
- Where competitors are cited.
Most-cited pagesin the report lists pages already inside the answers. Getting mentioned on one of those is usually cheaper than ranking a new page.
6. Diagnose why
For each lost high-intent prompt, work out the cause. Read
references/diagnosis.md — it gives the three root causes (coverage,
consistency, specificity), how to tell them apart from the evidence in the run,
and what each one implies. Produce a short diagnosis per prompt, not a generic
lecture.
7. Fix the technical layer
python scripts/aeo.py -w ./ai-visibility tech
Checks whether AI crawlers are allowed, whether llms.txt and sitemap.xml
exist, and what schema the homepage exposes. A blocked crawler makes every
other effort pointless, so this is the first thing to repair.
Generators for the fixes:
python scripts/aeo.py schema spec.json # JSON-LD, ready to paste
python scripts/aeo.py -w ./ai-visibility llmstxt --limit 40
references/technical-fixes.md covers robots.txt recipes per crawler, which
schema types matter, the llms.txt format, Bing Webmaster setup (ChatGPT
searches through Bing), and the GA4 channel-group regex for measuring AI
referral traffic.
8. Fix the content
For each page that should be winning a lost prompt:
python scripts/aeo.py -w ./ai-visibility page https://example.com/some-page
This extracts the signals that decide whether an assistant can lift a quotable
answer out of the page. Score them against the seven criteria in
references/content-rubric.md and give the user concrete rewrites — actual
sentences and markup they can paste, not advice to "improve clarity".
references/playbook.md holds the five levers behind all of this: clarity,
positioning, third-party presence, structure, and measurement. Consult it when
the user asks what to do rather than what is broken.
Re-running
Visibility is a trend, not a snapshot. Suggest a monthly re-run: same prompts,
same models, new run directory (--run 2026-09-01). The report shows the delta
automatically, and withholds it when the two runs used different models — a
comparison across different model sets is not a trend.
Command reference
| Command | Purpose |
|---|---|
init <domain> |
Create workspace, brand.json, prompts.json |
run [--models ...] [--run ID] |
Create empty answer files to fill in |
collect [--models ...] |
Fetch answers via OpenRouter (needs OPENROUTER_API_KEY) |
analyze [--run ID] |
Score answers into results.json |
report |
Build report.html + summary.md |
tech [domain] |
Crawler access, llms.txt, sitemap, homepage schema, AEO score |
page <url> |
Extract AEO signals from one page |
schema <spec.json> |
Generate JSON-LD |
llmstxt [--limit N] |
Generate llms.txt from the sitemap |
selftest |
Verify the detection logic (49 checks) |
Every command takes -w/--workspace and -h.
Working notes
- Answer in the user's language. The tooling is language-neutral; prompts,
descriptions, and your analysis should be in whatever language the user and
their buyers use. Bulgarian and English sentiment terms ship by default;
for other languages, extend
POSITIVE_WORDS/NEGATIVE_WORDSinscripts/aeolib.pyand addcontext_wordstobrand.json. - Sentiment is triage, not truth. It is a keyword tally. Treat every
negativeas a signal to read the raw answer, not as a finding. - Never invent numbers. Every figure in your summary must come from
results.jsonormetrics.json. If a run is thin, say so. - Empty answers are not misses. They are excluded from the denominator and reported separately. Do not let a half-filled run become "visibility dropped".
- One model is not a measurement. If the user only checked ChatGPT, say what that does and does not tell them before drawing conclusions.
- If outbound network access is unavailable (common in a sandboxed
environment),
tech,page,collect, andllmstxt --limitcannot fetch. Everything else — the whole measurement and diagnosis path — still works, because it reads pasted answers from disk. For the fetching commands, ask the user to paste the content instead:robots.txt,llms.txt, the page HTML, or the sitemap. Then analyze it directly rather than reporting a failure. Do not present a blocked fetch as a finding about their site.
File layout
<workspace>/
├── brand.json domain, aliases, keywords, competitors, positioning
├── prompts.json id, text, category, intent
├── runs/
│ └── 2026-08-01/
│ ├── responses/ p01__chatgpt.txt … one file per prompt × model
│ ├── results.json per-answer analysis
│ └── metrics.json aggregated metrics
├── report.html self-contained dashboard
├── summary.md same findings as markdown
└── technical-audit.json crawler / schema / llms.txt state
references/workspace.md documents every field and the answer-file format,
including how to import answers exported from another tool.