airanker
v0.1.5
Published
Measure how visible a brand is in AI/search results. Deterministic AI-brand-visibility scoring powered by Bright Data.
Maintainers
Readme
AIRanker
Measure how visible a brand is in AI and search results. Run a controlled set of queries across ChatGPT, Perplexity, Gemini, Google AI Mode and Google SERP, then get a deterministic, reproducible AI-visibility score for that brand — not a vibe, not one model's opinion: the same input always yields the same score.
npx airanker opencmo.siteWhat it does
- Analyzes the brand — fetches the website, uses an LLM to build a
grounded
BrandProfile(products, use-cases, keywords; competitors only if the site names them). - Generates prompts — a configurable number (default 8) of realistic user queries covering 9 intent classes (best-of, comparison, alternatives, problem/solution, buyer, …). Deduplicated.
- Collects raw evidence — each query is run against every AI engine plus Google SERP via Bright Data. Raw responses are stored verbatim.
- Extracts evidence — an LLM converts each raw response into structured
observations (mentioned? recommended? position? competitors?). Position is
nullunless the raw response states a real rank. The LLM never invents data — if extraction fails or the engine failed, the observation says so. - Scores deterministically — a pure function of the observations. No LLM in the scoring path. Formula is documented below.
- Explains — an LLM summarizes weaknesses/opportunities/competitor insights grounded only in the structured observations.
The three layers stay strictly separated: raw evidence (Bright Data) → LLM interpretation (extraction/summarization) → AIRanker score (deterministic arithmetic). Nothing is blurred.
Install / run
npm install -g airanker # or: npm i -D airanker
airanker opencmo.siteFirst run or any command checks your config. If Bright Data/LLM keys are missing, AIRanker walks you through setup interactively:
✓ AIRanker setup
Bright Data API key (won't be echoed): ••••••••••
Which LLM provider? ▸ Gemini API (Google AI Studio)
OpenAI API
Anthropic API
claude CLI (not detected)Values are written to ~/.airanker/.env (never printed to the terminal).
Commands
airanker opencmo.site # bare scan (equivalent to scan)
airanker scan opencmo.site # full scan
airanker scan opencmo.site --queries 100
airanker scan opencmo.site --queries 100 --output report.json
airanker scan opencmo.site --no-google
airanker scan opencmo.site --engines chatgpt,perplexity
airanker scan opencmo.site --raw # embed raw provider responses in JSON report
airanker report report.json # render a saved report
airanker setup # re-run interactive configuration
airanker --helpOffline demo (no keys, mock stack — tests/demo only):
airanker --demo opencmo.site--mock uses the mock Bright Data provider only (real LLM still required);
--demo uses mock Bright Data + mock LLM.
Required environment
| Variable | Purpose |
| --- | --- |
| BRIGHTDATA_API_KEY | Bright Data API key (required) |
| AIRANKER_LLM_PROVIDER | openai · anthropic · gemini · claude · codex · opencode |
| AIRANKER_OPENCODE_MODEL | opencode model, default opencode/deepseek-v4-flash-free |
| OPENAI_API_KEY | used when provider is openai |
| ANTHROPIC_API_KEY | used when provider is anthropic |
| GEMINI_API_KEY | Google AI Studio key (AIza...) |
If AIRANKER_LLM_PROVIDER is unset, the first available API key wins. Local CLI
providers (claude, codex, opencode) auto-detected on PATH; never
required, never assumed.
Copy .env.example to ~/.airanker/.env (or project .env) for the full list,
including dataset-id overrides, concurrency, timeouts and debug flags.
Bright Data setup
- Create an account at brightdata.com.
- SERP API zone (
serp_api1default) for Google results. - The AI-engine scrapers need no zone — they use the Scraper API
(
POST /datasets/v3/scrape) with dataset ids:- ChatGPT
gd_m7aof0k82r803d5bjm - Google AI Mode
gd_mcswdt6z2elth3zqr2 - Perplexity / Gemini / Copilot: auto-discovered from your account via
GET /datasets/v3/scrapers, or setBRIGHTDATA_PERPLEXITY_DATASET_ID/BRIGHTDATA_GEMINI_DATASET_ID/BRIGHTDATA_COPILOT_DATASET_ID.
- ChatGPT
- Grab the key at Settings → Users → API key (get.brightdata.com/ybqohltzjud2), then set it:
export BRIGHTDATA_API_KEY=your-key
airanker opencmo.siteLLM setup
Pick any one API provider. Keys are never logged; setup uses hidden prompts.
export OPENAI_API_KEY=sk-...
export GEMINI_API_KEY=AIza... # Google AI Studio
export ANTHROPIC_API_KEY=sk-ant-...
airanker scan opencmo.siteOr set AIRANKER_LLM_PROVIDER=gemini etc. You may also use a locally installed
claude / codex / opencode CLI — set AIRANKER_LLM_PROVIDER=claude.
Scoring methodology
Every observation contributes to four deterministic components. All are pure functions of the observations array.
mentionRate = mentioned queries / total queries
recommendRate = recommended queries / total queries
positionComp = 1 - clamp((avgPosition - 1) / 9, 0, 1) (positions known only)
→ falls back to mentionRate when no positions are known
competitComp = 1 - competitorShare
competitorShare = competitor mentions / (competitor mentions + brand recommendations)
AI Visibility = round(100 × (0.30·mentionRate + 0.30·recommendRate +
0.20·positionComp + 0.20·competitComp))- Position 1 → full position credit; position 10+ → zero; linear between.
- Avg. position uses only observations where the raw response stated a
position. Unknown positions are
null, never guessed. competitorSharemeasures how often competitors are recommended instead of the brand. No recommendations at all → competitors effectively "won" (share 1), which is the honest reading of an unseen brand.- Formula version is stored in every report (
airanker-v1), so scores stay comparable across releases.
JSON report
airanker scan opencmo.site --output report.jsonWrites a versioned, Zod-validated report:
{
"schemaVersion": 1,
"format": "airanker-report",
"report": {
"version": 1,
"generatedAt": "2026-08-17T00:00:00.000Z",
"brandProfile": { /* BrandProfile */ },
"queries": [ /* TestQuery */ ],
"observations": [ /* QueryObservation */ ],
"rawEvidence": [ /* only with --raw */ ],
"aggregateMetrics": { /* mentionRate, byEngine, byCategory, ... */ },
"score": { "overall": 74, "components": { ... }, "formulaVersion": "airanker-v1" },
"recommendations": { "summary": "...", "strengths": [], "weaknesses": [], ... }
}
}Render it later: airanker report report.json.
Architecture
DOMAIN
↓
Fetch/analyze website ──► LLM: BrandProfile (Zod)
↓
LLM: generate search queries (dedup)
↓
Bright Data: collect AI + SERP evidence (retries · timeouts · partial failure)
↓
LLM: extract structured observations (positions = null unless stated)
↓
Deterministic scoring engine (pure function, formula versioned)
↓
LLM: recommendations (grounded in observations only)
↓
CLI report + optional JSONsrc/
cli/ commander CLI, interactive setup, progress, Ctrl+C
providers/
brightnessdata/ BrightDataClient (Scraper + SERP APIs), mock provider
llm/ LLMProvider interface, OpenAi/Anthropic/Gemini, local CLIs
brand/ website fetch + BrandProfile
queries/ query generation + semantic-ish dedup
analysis/ evidence extraction (LLM as extractor, never oracle)
scoring/ pure deterministic scoring
recommendations/ grounded summarization
report/ stable versioned JSON + terminal renderer
config/ env loading (~/.airanker/.env + project .env)Providers are interchangeable: LLMProvider has one method
(generateStructured<T>({system, user, schema})) and BrightDataProvider has
collectAi / collectSerp. Swap in your own in under 20 lines.
Reliability & security
- Strict Zod validation on every structured LLM output;
nullposition rather than fabrication. - Retries with exponential backoff, request timeouts, concurrency limits,
structured debug logging (
--debug). - Partial scans: a failing engine becomes a
failedRawEvidenceentry; only a total provider outage aborts the scan. - Domain input sanitized (no URLs, paths or shell metachars reach fetch or prompts).
- Scraped content is treated as untrusted text — never executed.
- Subprocess execution restricted to an explicit allow-list
(
claude/codex/opencode) with fixed args, timeouts, and non-shell spawn. - API keys are never printed or logged; the logger scrubs key-shaped strings.
Testing
npm test # vitest · 38 tests, fixture-drivenCovers BrandProfile validation, query dedup, evidence extraction (incl. invalid/missing LLM JSON), scoring formulas, competitor aggregation, failed Bright Data requests, missing position, and partial scans.
Limitations
- AI/search responses are not deterministic universal rankings. Every engine personalizes answers by account, region, session and model. A scan is a sample at a point in time, not a ground-truth ranking. Re-run to see variance; that variance is itself information.
- Attempts bounded — a scan is an approximation over N queries × M engines.
- No dashboard, no accounts, no database — single-binary, local JSON only.
License
MIT. Open source by design — fork it, audit it, trust the score you computed yourself.
