# @mintel/seo-engine AI-powered SEO keyword discovery, topic clustering, competitor analysis, and content gap identification — grounded in real search data, zero hallucinations. ## Architecture ``` ProjectContext + SeoConfig │ ▼ ┌──────────────────────────────────────────┐ │ SEO Engine Orchestrator │ │ │ │ 1. Seed Query Expansion │ │ (company + industry + seedKeywords) │ │ │ │ 2. Data Collection (parallel) │ │ ├── Serper Search Agent │ │ │ (related searches, PAA, │ │ │ organic snippets, volume proxy) │ │ ├── Serper Autocomplete Agent │ │ │ (long-tail suggestions) │ │ └── Serper Competitor Agent │ │ (top-10 SERP positions) │ │ │ │ 3. LLM Evaluation (Gemini/Claude) │ │ → Strict context filtering │ │ → Topic Clustering + Intent Mapping │ │ │ │ 4. Content Gap Analysis (LLM) │ │ → Compare clusters vs existing pages │ │ → Identify missing content │ └──────────────────────────────────────────┘ │ ▼ SeoEngineOutput (clusters, gaps, competitors, discarded) ``` ## Quick Start ```typescript import { runSeoEngine } from "@mintel/seo-engine"; const result = await runSeoEngine( { companyName: "KLZ Cables", industry: "Mittelspannungskabel, Kabeltiefbau", briefing: "B2B provider of specialized medium-voltage cables.", targetAudience: "Bauleiter, Netzbetreiber", competitors: ["nkt.de", "faberkabel.de"], seedKeywords: ["NA2XS2Y", "VPE-isoliert"], existingPages: [ { url: "/produkte", title: "Produkte" }, { url: "/kontakt", title: "Kontakt" }, ], locale: { gl: "de", hl: "de" }, }, { serperApiKey: process.env.SERPER_API_KEY!, openRouterApiKey: process.env.OPENROUTER_API_KEY!, }, ); ``` ## Configuration ### `ProjectContext` | Field | Type | Description | | ------------------ | ----------------------------- | ------------------------------------------- | | `companyName` | `string?` | Client company name | | `industry` | `string?` | Industry / main focus keywords | | `briefing` | `string?` | Project briefing text | | `targetAudience` | `string?` | Who the content targets | | `competitors` | `string[]?` | Competitor domains to analyze | | `seedKeywords` | `string[]?` | Explicit seed keywords beyond auto-derived | | `existingPages` | `{ url, title }[]?` | Current site pages for content gap analysis | | `customGuidelines` | `string?` | Extra strict filtering rules for the LLM | | `locale` | `{ gl: string, hl: string }?` | Google locale (default: `de`) | ### `SeoConfig` | Field | Type | Description | | ------------------ | --------- | -------------------------------------------- | | `serperApiKey` | `string` | **Required.** Serper API key | | `openRouterApiKey` | `string` | **Required.** OpenRouter API key | | `model` | `string?` | LLM model (default: `google/gemini-2.5-pro`) | | `maxKeywords` | `number?` | Cap total keywords returned | ## Output ```typescript interface SeoEngineOutput { topicClusters: TopicCluster[]; // Grouped keywords with intent + scores competitorRankings: CompetitorRanking[]; // Who ranks for your terms contentGaps: ContentGap[]; // Missing pages you should create discardedTerms: string[]; // Terms filtered out (with reasons) } ``` ## Agents | Agent | Source | Data | | --------------------- | ---------------------- | ----------------------------------- | | `serper-agent` | Serper `/search` | Related searches, PAA, snippets | | `serper-autocomplete` | Serper `/autocomplete` | Google Autocomplete long-tail terms | | `serper-competitors` | Serper `/search` | Competitor SERP positions | ## API Keys - **Serper** — [serper.dev](https://serper.dev) (pay-per-search, ~$0.001/query) - **OpenRouter** — [openrouter.ai](https://openrouter.ai) (pay-per-token) No monthly subscriptions. Pure pay-on-demand. ## Development ```bash pnpm install # from monorepo root pnpm --filter @mintel/seo-engine build npx tsx src/test-run.ts # smoke test (needs API keys in .env) ```