npm.io
0.1.5 • Published 3 weeks agoCLI

airanker

Licence
MIT
Version
0.1.5
Deps
7
Size
355 kB
Vulns
0
Weekly
0
Stars
1

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.site

What it does

  1. 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).
  2. Generates prompts — a configurable number (default 8) of realistic user queries covering 9 intent classes (best-of, comparison, alternatives, problem/solution, buyer, …). Deduplicated.
  3. Collects raw evidence — each query is run against every AI engine plus Google SERP via Bright Data. Raw responses are stored verbatim.
  4. Extracts evidence — an LLM converts each raw response into structured observations (mentioned? recommended? position? competitors?). Position is null unless the raw response states a real rank. The LLM never invents data — if extraction fails or the engine failed, the observation says so.
  5. Scores deterministically — a pure function of the observations. No LLM in the scoring path. Formula is documented below.
  6. 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.site

First 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 --help

Offline 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
  1. Create an account at brightdata.com.
  2. SERP API zone (serp_api1 default) for Google results.
  3. 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 set BRIGHTDATA_PERPLEXITY_DATASET_ID / BRIGHTDATA_GEMINI_DATASET_ID / BRIGHTDATA_COPILOT_DATASET_ID.
  4. Grab the key at Settings → Users → API key (get.brightdata.com/ybqohltzjud2), then set it:
export BRIGHTDATA_API_KEY=your-key
airanker opencmo.site
LLM 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.site

Or 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.
  • competitorShare measures 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.json

Writes 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 JSON

src/

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; null position rather than fabrication.
  • Retries with exponential backoff, request timeouts, concurrency limits, structured debug logging (--debug).
  • Partial scans: a failing engine becomes a failed RawEvidence entry; 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-driven

Covers 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.

Keywords