Competitor Finder
by @adarshvmore
Identifies 3-5 competitors for a given brand using SerpAPI, DataForSEO, or OpenAI fallback, returning names, websites, and optional reasons for competition.
clawhub install competitor-finderπ About This Skill
Competitor Finder Skill
Purpose
Identifies 3-5 competitors for a given brand by searching the web via SerpAPI and, as a last resort, falling back to a minimal OpenAI call. Returns competitor names, websites, and optionally the reason they are considered competitors. This collector feeds into the Marketing Audit Pipeline to populate the Competitor Landscape section of the final report.Input Schema
// Function signature
collectCompetitors(brandName: string, domain?: string): Promise// brandName: The brand name to find competitors for (e.g. "Gymshark")
// domain: Optional domain for additional context (e.g. "gymshark.com").
// Helps refine competitor search and filter out the brand itself from results.
Output Schema
interface CompetitorData {
competitors: CompetitorEntry[]; // 3-5 competitor entries
error?: string; // Present only when collector fails
}interface CompetitorEntry {
name: string; // e.g. "Nike"
website: string; // e.g. "nike.com"
reason?: string; // e.g. "Direct competitor in activewear market"
}
API Dependencies
Primary: SerpAPI
https://serpapi.com/search.jsonSERPAPI_KEY environment variableSecondary: DataForSEO
https://api.dataforseo.com/v3/dataforseo_labs/google/competitors_domain/liveDATAFORSEO_LOGIN + DATAFORSEO_PASSWORD environment variablesFallback: OpenAI (minimal call)
gpt-4.1-miniOPENAI_API_KEY environment variableImplementation Pattern
Data Flow
1. ReceivebrandName and optional domain from the pipeline
2. Attempt Method 1: SerpAPI search
3. If Method 1 fails or returns insufficient results, attempt Method 2: DataForSEO
4. If both fail, attempt Method 3: OpenAI fallback (minimal prompt)
5. Deduplicate and filter results (remove the brand itself)
6. Return 3-5 competitors mapped to CompetitorDataMethod 1: SerpAPI Search
// Query: "top competitors of {brandName}"
{
api_key: process.env.SERPAPI_KEY,
engine: "google",
q: top competitors of ${brandName},
num: 10
}
Method 2: DataForSEO Competitor Domain
[{
target: domain, // e.g. "gymshark.com"
language_code: "en",
location_code: 2840, // United States
limit: 5
}]
Method 3: OpenAI Fallback (Minimal)
// ONLY used when Methods 1 and 2 both fail
// This is a MINIMAL prompt -- keep token usage as low as possible
const response = await openai.chat.completions.create({
model: 'gpt-4.1-mini',
max_tokens: 200,
temperature: 0.3,
messages: [
{
role: 'system',
content: 'You are a marketing analyst. Return only a JSON array of competitor objects.'
},
{
role: 'user',
content: List 5 direct competitors of "${brandName}"${domain ? (${domain}) : ''}. Return JSON: [{"name":"...","website":"...","reason":"..."}]
}
]
});
Result Filtering
name and website populatedError Handling
try/catchEMPTY_COMPETITOR_DATA with error field set: return { ...EMPTY_COMPETITOR_DATA, error: 'Competitor data unavailable: ' };
CompetitorData object logger.error('Competitor collector failed', { brandName, domain, method, err });
logger.warn('Competitor finder: SerpAPI failed, falling back to DataForSEO', { brandName });
logger.warn('Competitor finder: DataForSEO failed, falling back to OpenAI', { brandName });
Example Usage
import { collectCompetitors } from '../collectors/competitorCollector';// Successful collection (via SerpAPI)
const data = await collectCompetitors('Gymshark', 'gymshark.com');
// Returns:
// {
// competitors: [
// { name: "Nike", website: "nike.com", reason: "Global leader in athletic apparel" },
// { name: "Lululemon", website: "lululemon.com", reason: "Premium activewear competitor" },
// { name: "Under Armour", website: "underarmour.com", reason: "Direct competitor in gym wear" },
// { name: "Alphalete", website: "alphalete.com", reason: "DTC fitness apparel brand" },
// { name: "Fabletics", website: "fabletics.com", reason: "Subscription-based activewear" },
// ],
// }
// Partial result (only OpenAI fallback worked)
const partial = await collectCompetitors('ObscureBrand');
// Returns:
// {
// competitors: [
// { name: "CompetitorA", website: "competitora.com", reason: "Similar product category" },
// { name: "CompetitorB", website: "competitorb.com", reason: "Same target market" },
// { name: "CompetitorC", website: "competitorc.com" },
// ],
// }
// Failed collection (graceful degradation)
const failedData = await collectCompetitors('UnknownBrand');
// Returns:
// {
// competitors: [],
// error: "Competitor data unavailable: All methods failed"
// }
Notes
reportGenerator.ts where an AI model call is permitted. It must be minimal (max 200 tokens) and should be logged as a warning for cost monitoring.'instagram' (no domain available), skip Method 2 (DataForSEO requires a domain) and rely on Methods 1 and 3.EMPTY_COMPETITOR_DATA constant is defined in src/types/audit.types.ts and should be imported for fallback returns.π Tips & Best Practices
reportGenerator.ts where an AI model call is permitted. It must be minimal (max 200 tokens) and should be logged as a warning for cost monitoring.'instagram' (no domain available), skip Method 2 (DataForSEO requires a domain) and rely on Methods 1 and 3.EMPTY_COMPETITOR_DATA constant is defined in src/types/audit.types.ts and should be imported for fallback returns.