mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(ahrefs): align tool coverage and outputs with the Ahrefs API v3 (#5367)
* fix(ahrefs): align tool coverage and outputs with the Ahrefs API v3 - 5 of 8 tools were missing the required `select` param (API v3 rejects list-endpoint requests without it) and 3 sent a `date` param the endpoint doesn't accept - all list endpoints sent an `offset` param that doesn't exist on any Ahrefs v3 site-explorer endpoint (only `limit` is supported) - domain_rating and backlinks_stats read response fields at the wrong nesting level and always returned zeros; several other tools mapped output fields to column names the API doesn't return (position, url, traffic, backlinks, dofollow_backlinks, http_code) instead of the real ones (best_position, best_position_url, sum_traffic, links_to_target, dofollow_links, http_code_target) - keyword_overview used the wrong endpoint param entirely (`keyword` instead of `keywords`) so it always returned empty - added ahrefs_metrics (site-explorer/metrics) and ahrefs_organic_competitors (site-explorer/organic-competitors) tools - fixed docsLink to point at docs.sim.ai instead of ahrefs.com * fix(ahrefs): default metrics country to us like every other tool Greptile and Cursor Bugbot both flagged that ahrefs_metrics omitted the country when unset, unlike every other country-accepting Ahrefs tool, which silently returns global data instead of US data on direct tool calls. * fix(ahrefs): default history to all_time on backlinks and referring domains Cursor Bugbot flagged that history was only sent when explicitly set, even though the block UI defaults it to all_time — same class of gap as the metrics.ts country fix, applied for direct tool-call consistency. * feat(ahrefs): expose search intent flags on keyword overview Adds the real intents field (informational, navigational, commercial, transactional, branded, local) from keywords-explorer/overview, which was verified valid against the live API docs but not yet surfaced.
This commit is contained in:
+305
-343
@@ -3,6 +3,45 @@ import type { BlockConfig, BlockMeta } from '@/blocks/types'
|
||||
import { AuthMode, IntegrationType } from '@/blocks/types'
|
||||
import type { AhrefsResponse } from '@/tools/ahrefs/types'
|
||||
|
||||
const COUNTRY_OPTIONS = [
|
||||
{ label: 'United States', id: 'us' },
|
||||
{ label: 'United Kingdom', id: 'gb' },
|
||||
{ label: 'Germany', id: 'de' },
|
||||
{ label: 'France', id: 'fr' },
|
||||
{ label: 'Spain', id: 'es' },
|
||||
{ label: 'Italy', id: 'it' },
|
||||
{ label: 'Canada', id: 'ca' },
|
||||
{ label: 'Australia', id: 'au' },
|
||||
{ label: 'Japan', id: 'jp' },
|
||||
{ label: 'Brazil', id: 'br' },
|
||||
{ label: 'India', id: 'in' },
|
||||
{ label: 'Netherlands', id: 'nl' },
|
||||
{ label: 'Poland', id: 'pl' },
|
||||
{ label: 'Russia', id: 'ru' },
|
||||
{ label: 'Mexico', id: 'mx' },
|
||||
]
|
||||
|
||||
const MODE_OPTIONS = [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
{ label: 'Exact (exact URL)', id: 'exact' },
|
||||
]
|
||||
|
||||
const DATE_WAND_CONFIG = {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp' as const,
|
||||
}
|
||||
|
||||
export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
|
||||
type: 'ahrefs',
|
||||
name: 'Ahrefs',
|
||||
@@ -10,7 +49,7 @@ export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
|
||||
authMode: AuthMode.ApiKey,
|
||||
longDescription:
|
||||
'Integrate Ahrefs SEO tools into your workflow. Analyze domain ratings, backlinks, organic keywords, top pages, and more. Requires an Ahrefs Enterprise plan with API access.',
|
||||
docsLink: 'https://docs.ahrefs.com/docs/api/reference/introduction',
|
||||
docsLink: 'https://docs.sim.ai/integrations/ahrefs',
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Analytics,
|
||||
bgColor: '#FFFFFF',
|
||||
@@ -22,13 +61,15 @@ export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain Rating', id: 'ahrefs_domain_rating' },
|
||||
{ label: 'Metrics Overview', id: 'ahrefs_metrics' },
|
||||
{ label: 'Backlinks', id: 'ahrefs_backlinks' },
|
||||
{ label: 'Backlinks Stats', id: 'ahrefs_backlinks_stats' },
|
||||
{ label: 'Referring Domains', id: 'ahrefs_referring_domains' },
|
||||
{ label: 'Broken Backlinks', id: 'ahrefs_broken_backlinks' },
|
||||
{ label: 'Organic Keywords', id: 'ahrefs_organic_keywords' },
|
||||
{ label: 'Organic Competitors', id: 'ahrefs_organic_competitors' },
|
||||
{ label: 'Top Pages', id: 'ahrefs_top_pages' },
|
||||
{ label: 'Keyword Overview', id: 'ahrefs_keyword_overview' },
|
||||
{ label: 'Broken Backlinks', id: 'ahrefs_broken_backlinks' },
|
||||
],
|
||||
value: () => 'ahrefs_domain_rating',
|
||||
},
|
||||
@@ -48,19 +89,43 @@ export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_domain_rating' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
wandConfig: DATE_WAND_CONFIG,
|
||||
},
|
||||
// Metrics operation inputs
|
||||
{
|
||||
id: 'target',
|
||||
title: 'Target Domain/URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'example.com',
|
||||
condition: { field: 'operation', value: 'ahrefs_metrics' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: COUNTRY_OPTIONS,
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_metrics' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: MODE_OPTIONS,
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_metrics' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_metrics' },
|
||||
mode: 'advanced',
|
||||
wandConfig: DATE_WAND_CONFIG,
|
||||
},
|
||||
// Backlinks operation inputs
|
||||
{
|
||||
@@ -75,53 +140,31 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
{ label: 'Exact (exact URL)', id: 'exact' },
|
||||
],
|
||||
options: MODE_OPTIONS,
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_backlinks' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'history',
|
||||
title: 'History',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All time (includes lost backlinks)', id: 'all_time' },
|
||||
{ label: 'Live only', id: 'live' },
|
||||
],
|
||||
value: () => 'all_time',
|
||||
condition: { field: 'operation', value: 'ahrefs_backlinks' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
placeholder: '1000',
|
||||
condition: { field: 'operation', value: 'ahrefs_backlinks' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: 'ahrefs_backlinks' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_backlinks' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
// Backlinks Stats operation inputs
|
||||
{
|
||||
id: 'target',
|
||||
@@ -135,12 +178,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
{ label: 'Exact (exact URL)', id: 'exact' },
|
||||
],
|
||||
options: MODE_OPTIONS,
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_backlinks_stats' },
|
||||
mode: 'advanced',
|
||||
@@ -152,19 +190,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_backlinks_stats' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
wandConfig: DATE_WAND_CONFIG,
|
||||
},
|
||||
// Referring Domains operation inputs
|
||||
{
|
||||
@@ -179,256 +205,31 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
{ label: 'Exact (exact URL)', id: 'exact' },
|
||||
],
|
||||
options: MODE_OPTIONS,
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'history',
|
||||
title: 'History',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All time (includes lost domains)', id: 'all_time' },
|
||||
{ label: 'Live only', id: 'live' },
|
||||
],
|
||||
value: () => 'all_time',
|
||||
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
placeholder: '1000',
|
||||
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
// Organic Keywords operation inputs
|
||||
{
|
||||
id: 'target',
|
||||
title: 'Target Domain/URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'example.com',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'United States', id: 'us' },
|
||||
{ label: 'United Kingdom', id: 'gb' },
|
||||
{ label: 'Germany', id: 'de' },
|
||||
{ label: 'France', id: 'fr' },
|
||||
{ label: 'Spain', id: 'es' },
|
||||
{ label: 'Italy', id: 'it' },
|
||||
{ label: 'Canada', id: 'ca' },
|
||||
{ label: 'Australia', id: 'au' },
|
||||
{ label: 'Japan', id: 'jp' },
|
||||
{ label: 'Brazil', id: 'br' },
|
||||
{ label: 'India', id: 'in' },
|
||||
{ label: 'Netherlands', id: 'nl' },
|
||||
{ label: 'Poland', id: 'pl' },
|
||||
{ label: 'Russia', id: 'ru' },
|
||||
{ label: 'Mexico', id: 'mx' },
|
||||
],
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
{ label: 'Exact (exact URL)', id: 'exact' },
|
||||
],
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
// Top Pages operation inputs
|
||||
{
|
||||
id: 'target',
|
||||
title: 'Target Domain',
|
||||
type: 'short-input',
|
||||
placeholder: 'example.com',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'United States', id: 'us' },
|
||||
{ label: 'United Kingdom', id: 'gb' },
|
||||
{ label: 'Germany', id: 'de' },
|
||||
{ label: 'France', id: 'fr' },
|
||||
{ label: 'Spain', id: 'es' },
|
||||
{ label: 'Italy', id: 'it' },
|
||||
{ label: 'Canada', id: 'ca' },
|
||||
{ label: 'Australia', id: 'au' },
|
||||
{ label: 'Japan', id: 'jp' },
|
||||
{ label: 'Brazil', id: 'br' },
|
||||
{ label: 'India', id: 'in' },
|
||||
{ label: 'Netherlands', id: 'nl' },
|
||||
{ label: 'Poland', id: 'pl' },
|
||||
{ label: 'Russia', id: 'ru' },
|
||||
{ label: 'Mexico', id: 'mx' },
|
||||
],
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
],
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
// Keyword Overview operation inputs
|
||||
{
|
||||
id: 'keyword',
|
||||
title: 'Keyword',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter keyword to analyze',
|
||||
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'United States', id: 'us' },
|
||||
{ label: 'United Kingdom', id: 'gb' },
|
||||
{ label: 'Germany', id: 'de' },
|
||||
{ label: 'France', id: 'fr' },
|
||||
{ label: 'Spain', id: 'es' },
|
||||
{ label: 'Italy', id: 'it' },
|
||||
{ label: 'Canada', id: 'ca' },
|
||||
{ label: 'Australia', id: 'au' },
|
||||
{ label: 'Japan', id: 'jp' },
|
||||
{ label: 'Brazil', id: 'br' },
|
||||
{ label: 'India', id: 'in' },
|
||||
{ label: 'Netherlands', id: 'nl' },
|
||||
{ label: 'Poland', id: 'pl' },
|
||||
{ label: 'Russia', id: 'ru' },
|
||||
{ label: 'Mexico', id: 'mx' },
|
||||
],
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Broken Backlinks operation inputs
|
||||
{
|
||||
id: 'target',
|
||||
@@ -442,12 +243,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
{ label: 'Exact (exact URL)', id: 'exact' },
|
||||
],
|
||||
options: MODE_OPTIONS,
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
|
||||
mode: 'advanced',
|
||||
@@ -456,16 +252,43 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
placeholder: '1000',
|
||||
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Organic Keywords operation inputs
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
id: 'target',
|
||||
title: 'Target Domain/URL',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
|
||||
placeholder: 'example.com',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: COUNTRY_OPTIONS,
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: MODE_OPTIONS,
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '1000',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
@@ -473,21 +296,119 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
|
||||
mode: 'advanced',
|
||||
wandConfig: DATE_WAND_CONFIG,
|
||||
},
|
||||
// Organic Competitors operation inputs
|
||||
{
|
||||
id: 'target',
|
||||
title: 'Target Domain/URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'example.com',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: COUNTRY_OPTIONS,
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: MODE_OPTIONS,
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '1000',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
|
||||
mode: 'advanced',
|
||||
wandConfig: DATE_WAND_CONFIG,
|
||||
},
|
||||
// Top Pages operation inputs
|
||||
{
|
||||
id: 'target',
|
||||
title: 'Target Domain',
|
||||
type: 'short-input',
|
||||
placeholder: 'example.com',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: COUNTRY_OPTIONS,
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'mode',
|
||||
title: 'Analysis Mode',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Domain (entire domain)', id: 'domain' },
|
||||
{ label: 'Prefix (URL prefix)', id: 'prefix' },
|
||||
{ label: 'Subdomains (include all)', id: 'subdomains' },
|
||||
],
|
||||
value: () => 'domain',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '1000',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
title: 'Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to today)',
|
||||
condition: { field: 'operation', value: 'ahrefs_top_pages' },
|
||||
mode: 'advanced',
|
||||
wandConfig: DATE_WAND_CONFIG,
|
||||
},
|
||||
// Keyword Overview operation inputs
|
||||
{
|
||||
id: 'keyword',
|
||||
title: 'Keyword',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter keyword to analyze',
|
||||
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
title: 'Country',
|
||||
type: 'dropdown',
|
||||
options: COUNTRY_OPTIONS,
|
||||
value: () => 'us',
|
||||
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
|
||||
Examples:
|
||||
- "today" -> Current date in YYYY-MM-DD format
|
||||
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
|
||||
- "last week" -> Date 7 days ago in YYYY-MM-DD format
|
||||
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
|
||||
|
||||
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
// API Key (common to all operations)
|
||||
{
|
||||
@@ -502,33 +423,39 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
tools: {
|
||||
access: [
|
||||
'ahrefs_domain_rating',
|
||||
'ahrefs_metrics',
|
||||
'ahrefs_backlinks',
|
||||
'ahrefs_backlinks_stats',
|
||||
'ahrefs_referring_domains',
|
||||
'ahrefs_broken_backlinks',
|
||||
'ahrefs_organic_keywords',
|
||||
'ahrefs_organic_competitors',
|
||||
'ahrefs_top_pages',
|
||||
'ahrefs_keyword_overview',
|
||||
'ahrefs_broken_backlinks',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'ahrefs_domain_rating':
|
||||
return 'ahrefs_domain_rating'
|
||||
case 'ahrefs_metrics':
|
||||
return 'ahrefs_metrics'
|
||||
case 'ahrefs_backlinks':
|
||||
return 'ahrefs_backlinks'
|
||||
case 'ahrefs_backlinks_stats':
|
||||
return 'ahrefs_backlinks_stats'
|
||||
case 'ahrefs_referring_domains':
|
||||
return 'ahrefs_referring_domains'
|
||||
case 'ahrefs_broken_backlinks':
|
||||
return 'ahrefs_broken_backlinks'
|
||||
case 'ahrefs_organic_keywords':
|
||||
return 'ahrefs_organic_keywords'
|
||||
case 'ahrefs_organic_competitors':
|
||||
return 'ahrefs_organic_competitors'
|
||||
case 'ahrefs_top_pages':
|
||||
return 'ahrefs_top_pages'
|
||||
case 'ahrefs_keyword_overview':
|
||||
return 'ahrefs_keyword_overview'
|
||||
case 'ahrefs_broken_backlinks':
|
||||
return 'ahrefs_broken_backlinks'
|
||||
default:
|
||||
return 'ahrefs_domain_rating'
|
||||
}
|
||||
@@ -536,7 +463,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
params: (params) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
if (params.limit) result.limit = Number(params.limit)
|
||||
if (params.offset) result.offset = Number(params.offset)
|
||||
return result
|
||||
},
|
||||
},
|
||||
@@ -549,27 +475,46 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
mode: { type: 'string', description: 'Analysis mode (domain, prefix, subdomains, exact)' },
|
||||
country: { type: 'string', description: 'Country code for geo-specific data' },
|
||||
date: { type: 'string', description: 'Date for historical data in YYYY-MM-DD format' },
|
||||
history: {
|
||||
type: 'string',
|
||||
description: 'Historical scope for backlink-profile endpoints (all_time, live)',
|
||||
},
|
||||
limit: { type: 'number', description: 'Maximum number of results to return' },
|
||||
offset: { type: 'number', description: 'Number of results to skip for pagination' },
|
||||
},
|
||||
outputs: {
|
||||
// Domain Rating output
|
||||
domainRating: { type: 'number', description: 'Domain Rating score (0-100)' },
|
||||
ahrefsRank: { type: 'number', description: 'Ahrefs Rank (global ranking)' },
|
||||
// Metrics output
|
||||
metrics: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Organic and paid search overview (organicTraffic, organicKeywords, organicKeywordsTop3, organicCost, paidTraffic, paidKeywords, paidPages, paidCost)',
|
||||
},
|
||||
// Backlinks output
|
||||
backlinks: { type: 'json', description: 'List of backlinks' },
|
||||
// Backlinks Stats output
|
||||
stats: { type: 'json', description: 'Backlink statistics' },
|
||||
stats: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Backlink and referring domain totals (liveBacklinks, liveReferringDomains, allTimeBacklinks, allTimeReferringDomains)',
|
||||
},
|
||||
// Referring Domains output
|
||||
referringDomains: { type: 'json', description: 'List of referring domains' },
|
||||
// Broken Backlinks output
|
||||
brokenBacklinks: { type: 'json', description: 'List of broken backlinks' },
|
||||
// Organic Keywords output
|
||||
keywords: { type: 'json', description: 'List of organic keywords' },
|
||||
// Organic Competitors output
|
||||
competitors: { type: 'json', description: 'List of organic search competitors' },
|
||||
// Top Pages output
|
||||
pages: { type: 'json', description: 'List of top pages' },
|
||||
// Keyword Overview output
|
||||
overview: { type: 'json', description: 'Keyword metrics overview' },
|
||||
// Broken Backlinks output
|
||||
brokenBacklinks: { type: 'json', description: 'List of broken backlinks' },
|
||||
overview: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Keyword metrics overview, including search intent flags (informational, navigational, commercial, transactional, branded, local)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -615,6 +560,16 @@ export const AhrefsBlockMeta = {
|
||||
category: 'marketing',
|
||||
tags: ['marketing', 'automation'],
|
||||
},
|
||||
{
|
||||
icon: AhrefsIcon,
|
||||
title: 'Ahrefs organic competitor finder',
|
||||
prompt:
|
||||
'Build a monthly workflow that pulls Ahrefs organic competitors for my domain, cross-references them against my tracked competitor list, and posts newly surfaced competitors to Slack for review.',
|
||||
modules: ['scheduled', 'agent', 'workflows'],
|
||||
category: 'marketing',
|
||||
tags: ['marketing', 'research'],
|
||||
alsoIntegrations: ['slack'],
|
||||
},
|
||||
{
|
||||
icon: AhrefsIcon,
|
||||
title: 'Ahrefs + Similarweb growth scoreboard',
|
||||
@@ -668,5 +623,12 @@ export const AhrefsBlockMeta = {
|
||||
content:
|
||||
'# Track Organic Rankings\n\nReport how a domain is ranking in organic search using Ahrefs.\n\n## Steps\n1. Pull the organic keywords report for the target domain.\n2. Identify the top-ranking keywords and their positions.\n3. Compare against a prior snapshot if available to find gains and losses.\n\n## Output\nA summary of top organic keywords, notable position gains and drops, and pages that may need attention.',
|
||||
},
|
||||
{
|
||||
name: 'find-organic-competitors',
|
||||
description:
|
||||
'Use Ahrefs organic competitors data to identify sites competing for the same search traffic.',
|
||||
content:
|
||||
'# Find Organic Competitors\n\nSurface who actually competes with a domain in organic search using Ahrefs.\n\n## Steps\n1. Run an organic competitors report for the target domain.\n2. Rank results by common keyword overlap and competitor traffic.\n3. Cross-reference against the known competitor list to flag new entrants.\n\n## Output\nA ranked list of organic competitors with keyword overlap and traffic, highlighting any that are not yet being tracked.',
|
||||
},
|
||||
],
|
||||
} as const satisfies BlockMeta
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { AhrefsBacklinksParams, AhrefsBacklinksResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'url_from,url_to,anchor,domain_rating_source,is_dofollow,first_seen,last_visited'
|
||||
|
||||
export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksResponse> = {
|
||||
id: 'ahrefs_backlinks',
|
||||
name: 'Ahrefs Backlinks',
|
||||
@@ -21,25 +24,20 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
history: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Historical scope: "live" (currently live backlinks), "all_time" (default, includes lost backlinks), or "since:YYYY-MM-DD" (backlinks found since a date).',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 100)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination. Example: 100',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
@@ -51,14 +49,12 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/backlinks')
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/all-backlinks')
|
||||
url.searchParams.set('target', params.target)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
url.searchParams.set('history', params.history || 'all_time')
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
@@ -79,8 +75,8 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
|
||||
urlFrom: link.url_from || '',
|
||||
urlTo: link.url_to || '',
|
||||
anchor: link.anchor || '',
|
||||
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
|
||||
isDofollow: link.is_dofollow ?? link.dofollow ?? false,
|
||||
domainRatingSource: link.domain_rating_source ?? 0,
|
||||
isDofollow: link.is_dofollow ?? false,
|
||||
firstSeen: link.first_seen || '',
|
||||
lastVisited: link.last_visited || '',
|
||||
}))
|
||||
|
||||
@@ -8,7 +8,7 @@ export const backlinksStatsTool: ToolConfig<
|
||||
id: 'ahrefs_backlinks_stats',
|
||||
name: 'Ahrefs Backlinks Stats',
|
||||
description:
|
||||
'Get backlink statistics for a target domain or URL. Returns totals for different backlink types including dofollow, nofollow, text, image, and redirect links.',
|
||||
'Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
@@ -24,13 +24,13 @@ export const backlinksStatsTool: ToolConfig<
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
|
||||
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
@@ -64,16 +64,16 @@ export const backlinksStatsTool: ToolConfig<
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get backlinks stats')
|
||||
}
|
||||
|
||||
const metrics = data.metrics || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
stats: {
|
||||
total: data.live ?? data.total ?? 0,
|
||||
dofollow: data.live_dofollow ?? data.dofollow ?? 0,
|
||||
nofollow: data.live_nofollow ?? data.nofollow ?? 0,
|
||||
text: data.text ?? 0,
|
||||
image: data.image ?? 0,
|
||||
redirect: data.redirect ?? 0,
|
||||
liveBacklinks: metrics.live ?? 0,
|
||||
liveReferringDomains: metrics.live_refdomains ?? 0,
|
||||
allTimeBacklinks: metrics.all_time ?? 0,
|
||||
allTimeReferringDomains: metrics.all_time_refdomains ?? 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -82,14 +82,21 @@ export const backlinksStatsTool: ToolConfig<
|
||||
outputs: {
|
||||
stats: {
|
||||
type: 'object',
|
||||
description: 'Backlink statistics summary',
|
||||
description: 'Backlink and referring domain totals',
|
||||
properties: {
|
||||
total: { type: 'number', description: 'Total number of live backlinks' },
|
||||
dofollow: { type: 'number', description: 'Number of dofollow backlinks' },
|
||||
nofollow: { type: 'number', description: 'Number of nofollow backlinks' },
|
||||
text: { type: 'number', description: 'Number of text backlinks' },
|
||||
image: { type: 'number', description: 'Number of image backlinks' },
|
||||
redirect: { type: 'number', description: 'Number of redirect backlinks' },
|
||||
liveBacklinks: { type: 'number', description: 'Number of currently live backlinks' },
|
||||
liveReferringDomains: {
|
||||
type: 'number',
|
||||
description: 'Number of currently live referring domains',
|
||||
},
|
||||
allTimeBacklinks: {
|
||||
type: 'number',
|
||||
description: 'Total backlinks ever discovered, including lost ones',
|
||||
},
|
||||
allTimeReferringDomains: {
|
||||
type: 'number',
|
||||
description: 'Total referring domains ever discovered, including lost ones',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,8 @@ import type {
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'url_from,url_to,http_code_target,anchor,domain_rating_source'
|
||||
|
||||
export const brokenBacklinksTool: ToolConfig<
|
||||
AhrefsBrokenBacklinksParams,
|
||||
AhrefsBrokenBacklinksResponse
|
||||
@@ -27,25 +29,13 @@ export const brokenBacklinksTool: ToolConfig<
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 100)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination. Example: 100',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
@@ -59,12 +49,9 @@ export const brokenBacklinksTool: ToolConfig<
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/broken-backlinks')
|
||||
url.searchParams.set('target', params.target)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
@@ -81,12 +68,12 @@ export const brokenBacklinksTool: ToolConfig<
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get broken backlinks')
|
||||
}
|
||||
|
||||
const brokenBacklinks = (data.backlinks || data.broken_backlinks || []).map((link: any) => ({
|
||||
const brokenBacklinks = (data.backlinks || []).map((link: any) => ({
|
||||
urlFrom: link.url_from || '',
|
||||
urlTo: link.url_to || '',
|
||||
httpCode: link.http_code ?? link.status_code ?? 404,
|
||||
httpCode: link.http_code_target ?? null,
|
||||
anchor: link.anchor || '',
|
||||
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
|
||||
domainRatingSource: link.domain_rating_source ?? 0,
|
||||
}))
|
||||
|
||||
return {
|
||||
@@ -109,7 +96,11 @@ export const brokenBacklinksTool: ToolConfig<
|
||||
description: 'The URL of the page containing the broken link',
|
||||
},
|
||||
urlTo: { type: 'string', description: 'The broken URL being linked to' },
|
||||
httpCode: { type: 'number', description: 'HTTP status code (e.g., 404, 410)' },
|
||||
httpCode: {
|
||||
type: 'number',
|
||||
description: 'HTTP status code of the broken target URL (e.g., 404, 410)',
|
||||
optional: true,
|
||||
},
|
||||
anchor: { type: 'string', description: 'The anchor text of the link' },
|
||||
domainRatingSource: {
|
||||
type: 'number',
|
||||
|
||||
@@ -55,8 +55,8 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
domainRating: data.domain_rating ?? 0,
|
||||
ahrefsRank: data.ahrefs_rank ?? 0,
|
||||
domainRating: data.domain_rating?.domain_rating ?? 0,
|
||||
ahrefsRank: data.domain_rating?.ahrefs_rank ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -69,6 +69,7 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
|
||||
ahrefsRank: {
|
||||
type: 'number',
|
||||
description: 'Ahrefs Rank - global ranking based on backlink profile strength',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { backlinksStatsTool } from '@/tools/ahrefs/backlinks_stats'
|
||||
import { brokenBacklinksTool } from '@/tools/ahrefs/broken_backlinks'
|
||||
import { domainRatingTool } from '@/tools/ahrefs/domain_rating'
|
||||
import { keywordOverviewTool } from '@/tools/ahrefs/keyword_overview'
|
||||
import { metricsTool } from '@/tools/ahrefs/metrics'
|
||||
import { organicCompetitorsTool } from '@/tools/ahrefs/organic_competitors'
|
||||
import { organicKeywordsTool } from '@/tools/ahrefs/organic_keywords'
|
||||
import { referringDomainsTool } from '@/tools/ahrefs/referring_domains'
|
||||
import { topPagesTool } from '@/tools/ahrefs/top_pages'
|
||||
@@ -15,3 +17,7 @@ export const ahrefsOrganicKeywordsTool = organicKeywordsTool
|
||||
export const ahrefsTopPagesTool = topPagesTool
|
||||
export const ahrefsKeywordOverviewTool = keywordOverviewTool
|
||||
export const ahrefsBrokenBacklinksTool = brokenBacklinksTool
|
||||
export const ahrefsMetricsTool = metricsTool
|
||||
export const ahrefsOrganicCompetitorsTool = organicCompetitorsTool
|
||||
|
||||
export * from '@/tools/ahrefs/types'
|
||||
|
||||
@@ -4,6 +4,9 @@ import type {
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'keyword,volume,difficulty,cpc,clicks,searches_pct_clicks_organic_only,parent_topic,traffic_potential,intents'
|
||||
|
||||
export const keywordOverviewTool: ToolConfig<
|
||||
AhrefsKeywordOverviewParams,
|
||||
AhrefsKeywordOverviewResponse
|
||||
@@ -38,8 +41,9 @@ export const keywordOverviewTool: ToolConfig<
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/overview')
|
||||
url.searchParams.set('keyword', params.keyword)
|
||||
url.searchParams.set('keywords', params.keyword)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
@@ -56,18 +60,21 @@ export const keywordOverviewTool: ToolConfig<
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get keyword overview')
|
||||
}
|
||||
|
||||
const result = (data.keywords || [])[0] || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
overview: {
|
||||
keyword: data.keyword || '',
|
||||
searchVolume: data.volume ?? 0,
|
||||
keywordDifficulty: data.keyword_difficulty ?? data.difficulty ?? 0,
|
||||
cpc: data.cpc ?? 0,
|
||||
clicks: data.clicks ?? 0,
|
||||
clicksPercentage: data.clicks_percentage ?? 0,
|
||||
parentTopic: data.parent_topic || '',
|
||||
trafficPotential: data.traffic_potential ?? 0,
|
||||
keyword: result.keyword || '',
|
||||
searchVolume: result.volume ?? 0,
|
||||
keywordDifficulty: result.difficulty ?? null,
|
||||
cpc: result.cpc ?? null,
|
||||
clicks: result.clicks ?? null,
|
||||
clicksPercentage: result.searches_pct_clicks_organic_only ?? null,
|
||||
parentTopic: result.parent_topic ?? null,
|
||||
trafficPotential: result.traffic_potential ?? null,
|
||||
intents: result.intents ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -83,17 +90,38 @@ export const keywordOverviewTool: ToolConfig<
|
||||
keywordDifficulty: {
|
||||
type: 'number',
|
||||
description: 'Keyword difficulty score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
cpc: { type: 'number', description: 'Cost per click in USD' },
|
||||
clicks: { type: 'number', description: 'Estimated clicks per month' },
|
||||
cpc: { type: 'number', description: 'Cost per click in USD', optional: true },
|
||||
clicks: { type: 'number', description: 'Estimated clicks per month', optional: true },
|
||||
clicksPercentage: {
|
||||
type: 'number',
|
||||
description: 'Percentage of searches that result in clicks',
|
||||
description: 'Percentage of searches that result in an organic click',
|
||||
optional: true,
|
||||
},
|
||||
parentTopic: {
|
||||
type: 'string',
|
||||
description: 'The parent topic for this keyword',
|
||||
optional: true,
|
||||
},
|
||||
parentTopic: { type: 'string', description: 'The parent topic for this keyword' },
|
||||
trafficPotential: {
|
||||
type: 'number',
|
||||
description: 'Estimated traffic potential if ranking #1',
|
||||
optional: true,
|
||||
},
|
||||
intents: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Search intent flags (informational, navigational, commercial, transactional, branded, local)',
|
||||
optional: true,
|
||||
properties: {
|
||||
informational: { type: 'boolean', description: 'Query seeks information' },
|
||||
navigational: { type: 'boolean', description: 'Query seeks a specific site or page' },
|
||||
commercial: { type: 'boolean', description: 'Query researches a purchase decision' },
|
||||
transactional: { type: 'boolean', description: 'Query intends to complete a purchase' },
|
||||
branded: { type: 'boolean', description: 'Query references a specific brand' },
|
||||
local: { type: 'boolean', description: 'Query seeks local results' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { AhrefsMetricsParams, AhrefsMetricsResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const metricsTool: ToolConfig<AhrefsMetricsParams, AhrefsMetricsResponse> = {
|
||||
id: 'ahrefs_metrics',
|
||||
name: 'Ahrefs Metrics',
|
||||
description:
|
||||
'Get a one-call organic and paid search overview for a target domain or URL: organic traffic, organic keywords, paid traffic, paid keywords, and estimated traffic cost.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for traffic data. Example: "us", "gb", "de"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/metrics')
|
||||
url.searchParams.set('target', params.target)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get metrics')
|
||||
}
|
||||
|
||||
const metrics = data.metrics || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
metrics: {
|
||||
organicTraffic: metrics.org_traffic ?? 0,
|
||||
organicKeywords: metrics.org_keywords ?? 0,
|
||||
organicKeywordsTop3: metrics.org_keywords_1_3 ?? 0,
|
||||
organicCost: metrics.org_cost ?? null,
|
||||
paidTraffic: metrics.paid_traffic ?? 0,
|
||||
paidKeywords: metrics.paid_keywords ?? 0,
|
||||
paidPages: metrics.paid_pages ?? 0,
|
||||
paidCost: metrics.paid_cost ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'Organic and paid search overview',
|
||||
properties: {
|
||||
organicTraffic: { type: 'number', description: 'Estimated monthly organic traffic' },
|
||||
organicKeywords: { type: 'number', description: 'Number of organic keywords ranked' },
|
||||
organicKeywordsTop3: {
|
||||
type: 'number',
|
||||
description: 'Number of organic keywords ranking in positions 1-3',
|
||||
},
|
||||
organicCost: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly cost to replicate organic traffic via ads (USD)',
|
||||
optional: true,
|
||||
},
|
||||
paidTraffic: { type: 'number', description: 'Estimated monthly paid search traffic' },
|
||||
paidKeywords: { type: 'number', description: 'Number of paid keywords targeted' },
|
||||
paidPages: { type: 'number', description: 'Number of pages receiving paid traffic' },
|
||||
paidCost: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly paid search spend (USD)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import type {
|
||||
AhrefsOrganicCompetitorsParams,
|
||||
AhrefsOrganicCompetitorsResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'competitor_domain,domain_rating,keywords_common,keywords_target,keywords_competitor,traffic'
|
||||
|
||||
export const organicCompetitorsTool: ToolConfig<
|
||||
AhrefsOrganicCompetitorsParams,
|
||||
AhrefsOrganicCompetitorsResponse
|
||||
> = {
|
||||
id: 'ahrefs_organic_competitors',
|
||||
name: 'Ahrefs Organic Competitors',
|
||||
description:
|
||||
'Get domains that compete with a target domain or URL for the same organic keywords, ranked by keyword overlap.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for search results. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/organic-competitors')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get organic competitors')
|
||||
}
|
||||
|
||||
const competitors = (data.competitors || []).map((competitor: any) => ({
|
||||
domain: competitor.competitor_domain ?? null,
|
||||
domainRating: competitor.domain_rating ?? 0,
|
||||
commonKeywords: competitor.keywords_common ?? 0,
|
||||
targetKeywords: competitor.keywords_target ?? 0,
|
||||
competitorKeywords: competitor.keywords_competitor ?? 0,
|
||||
traffic: competitor.traffic ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
competitors,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
competitors: {
|
||||
type: 'array',
|
||||
description: 'List of organic search competitors ranked by keyword overlap',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
description: 'The competitor domain',
|
||||
optional: true,
|
||||
},
|
||||
domainRating: { type: 'number', description: 'Domain Rating of the competitor' },
|
||||
commonKeywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the competitor and target both rank for',
|
||||
},
|
||||
targetKeywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the target ranks for',
|
||||
},
|
||||
competitorKeywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the competitor ranks for',
|
||||
},
|
||||
traffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly organic traffic for the competitor',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import type {
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'keyword,volume,best_position,best_position_url,sum_traffic,keyword_difficulty'
|
||||
|
||||
export const organicKeywordsTool: ToolConfig<
|
||||
AhrefsOrganicKeywordsParams,
|
||||
AhrefsOrganicKeywordsResponse
|
||||
@@ -33,25 +36,19 @@ export const organicKeywordsTool: ToolConfig<
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
|
||||
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 100)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination. Example: 100',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
@@ -66,12 +63,12 @@ export const organicKeywordsTool: ToolConfig<
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/organic-keywords')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
@@ -88,13 +85,13 @@ export const organicKeywordsTool: ToolConfig<
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get organic keywords')
|
||||
}
|
||||
|
||||
const keywords = (data.keywords || data.organic_keywords || []).map((kw: any) => ({
|
||||
const keywords = (data.keywords || []).map((kw: any) => ({
|
||||
keyword: kw.keyword || '',
|
||||
volume: kw.volume ?? 0,
|
||||
position: kw.position ?? 0,
|
||||
url: kw.url || '',
|
||||
traffic: kw.traffic ?? 0,
|
||||
keywordDifficulty: kw.keyword_difficulty ?? kw.difficulty ?? 0,
|
||||
position: kw.best_position ?? null,
|
||||
url: kw.best_position_url ?? null,
|
||||
traffic: kw.sum_traffic ?? 0,
|
||||
keywordDifficulty: kw.keyword_difficulty ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
@@ -114,12 +111,21 @@ export const organicKeywordsTool: ToolConfig<
|
||||
properties: {
|
||||
keyword: { type: 'string', description: 'The keyword' },
|
||||
volume: { type: 'number', description: 'Monthly search volume' },
|
||||
position: { type: 'number', description: 'Current ranking position' },
|
||||
url: { type: 'string', description: 'The URL that ranks for this keyword' },
|
||||
position: {
|
||||
type: 'number',
|
||||
description: 'Best ranking position for this keyword',
|
||||
optional: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'The URL that ranks at the best position for this keyword',
|
||||
optional: true,
|
||||
},
|
||||
traffic: { type: 'number', description: 'Estimated monthly organic traffic' },
|
||||
keywordDifficulty: {
|
||||
type: 'number',
|
||||
description: 'Keyword difficulty score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,8 @@ import type {
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'domain,domain_rating,links_to_target,dofollow_links,first_seen,last_seen'
|
||||
|
||||
export const referringDomainsTool: ToolConfig<
|
||||
AhrefsReferringDomainsParams,
|
||||
AhrefsReferringDomainsResponse
|
||||
@@ -27,25 +29,20 @@ export const referringDomainsTool: ToolConfig<
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
history: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Historical scope: "live" (currently live), "all_time" (default, includes lost domains), or "since:YYYY-MM-DD" (domains found since a date).',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 100)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination. Example: 100',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
@@ -59,12 +56,10 @@ export const referringDomainsTool: ToolConfig<
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/refdomains')
|
||||
url.searchParams.set('target', params.target)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
url.searchParams.set('history', params.history || 'all_time')
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
@@ -81,16 +76,14 @@ export const referringDomainsTool: ToolConfig<
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get referring domains')
|
||||
}
|
||||
|
||||
const referringDomains = (data.refdomains || data.referring_domains || []).map(
|
||||
(domain: any) => ({
|
||||
domain: domain.domain || domain.refdomain || '',
|
||||
domainRating: domain.domain_rating ?? 0,
|
||||
backlinks: domain.backlinks ?? 0,
|
||||
dofollowBacklinks: domain.dofollow_backlinks ?? domain.dofollow ?? 0,
|
||||
firstSeen: domain.first_seen || '',
|
||||
lastVisited: domain.last_visited || '',
|
||||
})
|
||||
)
|
||||
const referringDomains = (data.refdomains || []).map((domain: any) => ({
|
||||
domain: domain.domain || '',
|
||||
domainRating: domain.domain_rating ?? 0,
|
||||
backlinks: domain.links_to_target ?? 0,
|
||||
dofollowBacklinks: domain.dofollow_links ?? 0,
|
||||
firstSeen: domain.first_seen || '',
|
||||
lastVisited: domain.last_seen ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -111,14 +104,18 @@ export const referringDomainsTool: ToolConfig<
|
||||
domainRating: { type: 'number', description: 'Domain Rating of the referring domain' },
|
||||
backlinks: {
|
||||
type: 'number',
|
||||
description: 'Total number of backlinks from this domain',
|
||||
description: 'Total number of backlinks from this domain to the target',
|
||||
},
|
||||
dofollowBacklinks: {
|
||||
type: 'number',
|
||||
description: 'Number of dofollow backlinks from this domain',
|
||||
},
|
||||
firstSeen: { type: 'string', description: 'When the domain was first seen linking' },
|
||||
lastVisited: { type: 'string', description: 'When the domain was last checked' },
|
||||
lastVisited: {
|
||||
type: 'string',
|
||||
description: 'When the domain was last seen linking (null if never re-crawled)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { AhrefsTopPagesParams, AhrefsTopPagesResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'url,sum_traffic,keywords,top_keyword,value'
|
||||
|
||||
export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesResponse> = {
|
||||
id: 'ahrefs_top_pages',
|
||||
name: 'Ahrefs Top Pages',
|
||||
@@ -26,32 +28,19 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains). Example: "domain"',
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default). Example: "domain"',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
|
||||
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 100)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination. Example: 100',
|
||||
},
|
||||
select: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Comma-separated list of fields to return (e.g., url,traffic,keywords,top_keyword,value). Default: url,traffic,keywords,top_keyword,value',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
@@ -66,15 +55,12 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/top-pages')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
// Select is required by API v3 - default to common fields if not provided
|
||||
const select = params.select || 'url,traffic,keywords,top_keyword,value'
|
||||
url.searchParams.set('select', select)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
@@ -91,12 +77,12 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get top pages')
|
||||
}
|
||||
|
||||
const pages = (data.pages || data.top_pages || []).map((page: any) => ({
|
||||
url: page.url || '',
|
||||
traffic: page.traffic ?? 0,
|
||||
keywords: page.keywords ?? page.keyword_count ?? 0,
|
||||
topKeyword: page.top_keyword || '',
|
||||
value: page.value ?? page.traffic_value ?? 0,
|
||||
const pages = (data.pages || []).map((page: any) => ({
|
||||
url: page.url ?? null,
|
||||
traffic: page.sum_traffic ?? 0,
|
||||
keywords: page.keywords ?? null,
|
||||
topKeyword: page.top_keyword ?? null,
|
||||
value: page.value ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
@@ -114,14 +100,23 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'The page URL' },
|
||||
url: { type: 'string', description: 'The page URL', optional: true },
|
||||
traffic: { type: 'number', description: 'Estimated monthly organic traffic' },
|
||||
keywords: { type: 'number', description: 'Number of keywords the page ranks for' },
|
||||
keywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the page ranks for',
|
||||
optional: true,
|
||||
},
|
||||
topKeyword: {
|
||||
type: 'string',
|
||||
description: 'The top keyword driving traffic to this page',
|
||||
optional: true,
|
||||
},
|
||||
value: {
|
||||
type: 'number',
|
||||
description: 'Estimated traffic value in USD',
|
||||
optional: true,
|
||||
},
|
||||
value: { type: 'number', description: 'Estimated traffic value in USD' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,26 +4,24 @@ import type { ToolResponse } from '@/tools/types'
|
||||
// Common parameters for all Ahrefs tools
|
||||
interface AhrefsBaseParams {
|
||||
apiKey: string
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
}
|
||||
|
||||
// Target mode for analysis
|
||||
export type AhrefsTargetMode = 'domain' | 'prefix' | 'subdomains' | 'exact'
|
||||
|
||||
// Historical scope for backlink-profile endpoints (no `date` param on these endpoints)
|
||||
export type AhrefsHistory = 'live' | 'all_time' | string // `since:YYYY-MM-DD` is also valid
|
||||
|
||||
// Domain Rating tool types
|
||||
export interface AhrefsDomainRatingParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
}
|
||||
|
||||
interface AhrefsDomainRatingResult {
|
||||
domain_rating: number
|
||||
ahrefs_rank: number
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
}
|
||||
|
||||
export interface AhrefsDomainRatingResponse extends ToolResponse {
|
||||
output: {
|
||||
domainRating: number
|
||||
ahrefsRank: number
|
||||
ahrefsRank: number | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +29,8 @@ export interface AhrefsDomainRatingResponse extends ToolResponse {
|
||||
export interface AhrefsBacklinksParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
history?: AhrefsHistory
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
interface AhrefsBacklink {
|
||||
@@ -55,15 +53,14 @@ export interface AhrefsBacklinksResponse extends ToolResponse {
|
||||
export interface AhrefsBacklinksStatsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
}
|
||||
|
||||
interface AhrefsBacklinksStatsResult {
|
||||
total: number
|
||||
dofollow: number
|
||||
nofollow: number
|
||||
text: number
|
||||
image: number
|
||||
redirect: number
|
||||
liveBacklinks: number
|
||||
liveReferringDomains: number
|
||||
allTimeBacklinks: number
|
||||
allTimeReferringDomains: number
|
||||
}
|
||||
|
||||
export interface AhrefsBacklinksStatsResponse extends ToolResponse {
|
||||
@@ -76,8 +73,8 @@ export interface AhrefsBacklinksStatsResponse extends ToolResponse {
|
||||
export interface AhrefsReferringDomainsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
history?: AhrefsHistory
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
interface AhrefsReferringDomain {
|
||||
@@ -86,7 +83,7 @@ interface AhrefsReferringDomain {
|
||||
backlinks: number
|
||||
dofollowBacklinks: number
|
||||
firstSeen: string
|
||||
lastVisited: string
|
||||
lastVisited: string | null
|
||||
}
|
||||
|
||||
export interface AhrefsReferringDomainsResponse extends ToolResponse {
|
||||
@@ -100,17 +97,17 @@ export interface AhrefsOrganicKeywordsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
interface AhrefsOrganicKeyword {
|
||||
keyword: string
|
||||
volume: number
|
||||
position: number
|
||||
url: string
|
||||
position: number | null
|
||||
url: string | null
|
||||
traffic: number
|
||||
keywordDifficulty: number
|
||||
keywordDifficulty: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsOrganicKeywordsResponse extends ToolResponse {
|
||||
@@ -124,17 +121,16 @@ export interface AhrefsTopPagesParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
limit?: number
|
||||
offset?: number
|
||||
select?: string // Comma-separated list of fields to return (e.g., "url,traffic,keywords,top_keyword,value")
|
||||
}
|
||||
|
||||
interface AhrefsTopPage {
|
||||
url: string
|
||||
url: string | null
|
||||
traffic: number
|
||||
keywords: number
|
||||
topKeyword: string
|
||||
value: number
|
||||
keywords: number | null
|
||||
topKeyword: string | null
|
||||
value: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsTopPagesResponse extends ToolResponse {
|
||||
@@ -149,15 +145,25 @@ export interface AhrefsKeywordOverviewParams extends AhrefsBaseParams {
|
||||
country?: string
|
||||
}
|
||||
|
||||
interface AhrefsKeywordIntents {
|
||||
informational: boolean
|
||||
navigational: boolean
|
||||
commercial: boolean
|
||||
transactional: boolean
|
||||
branded: boolean
|
||||
local: boolean
|
||||
}
|
||||
|
||||
interface AhrefsKeywordOverviewResult {
|
||||
keyword: string
|
||||
searchVolume: number
|
||||
keywordDifficulty: number
|
||||
cpc: number
|
||||
clicks: number
|
||||
clicksPercentage: number
|
||||
parentTopic: string
|
||||
trafficPotential: number
|
||||
keywordDifficulty: number | null
|
||||
cpc: number | null
|
||||
clicks: number | null
|
||||
clicksPercentage: number | null
|
||||
parentTopic: string | null
|
||||
trafficPotential: number | null
|
||||
intents: AhrefsKeywordIntents | null
|
||||
}
|
||||
|
||||
export interface AhrefsKeywordOverviewResponse extends ToolResponse {
|
||||
@@ -171,13 +177,12 @@ export interface AhrefsBrokenBacklinksParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
interface AhrefsBrokenBacklink {
|
||||
urlFrom: string
|
||||
urlTo: string
|
||||
httpCode: number
|
||||
httpCode: number | null
|
||||
anchor: string
|
||||
domainRatingSource: number
|
||||
}
|
||||
@@ -188,6 +193,55 @@ export interface AhrefsBrokenBacklinksResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics tool types (single-call organic + paid search overview)
|
||||
export interface AhrefsMetricsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
}
|
||||
|
||||
interface AhrefsMetricsResult {
|
||||
organicTraffic: number
|
||||
organicKeywords: number
|
||||
organicKeywordsTop3: number
|
||||
organicCost: number | null
|
||||
paidTraffic: number
|
||||
paidKeywords: number
|
||||
paidPages: number
|
||||
paidCost: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsMetricsResponse extends ToolResponse {
|
||||
output: {
|
||||
metrics: AhrefsMetricsResult
|
||||
}
|
||||
}
|
||||
|
||||
// Organic Competitors tool types
|
||||
export interface AhrefsOrganicCompetitorsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsOrganicCompetitor {
|
||||
domain: string | null
|
||||
domainRating: number
|
||||
commonKeywords: number
|
||||
targetKeywords: number
|
||||
competitorKeywords: number
|
||||
traffic: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsOrganicCompetitorsResponse extends ToolResponse {
|
||||
output: {
|
||||
competitors: AhrefsOrganicCompetitor[]
|
||||
}
|
||||
}
|
||||
|
||||
// Union type for all possible responses
|
||||
export type AhrefsResponse =
|
||||
| AhrefsDomainRatingResponse
|
||||
@@ -198,3 +252,5 @@ export type AhrefsResponse =
|
||||
| AhrefsTopPagesResponse
|
||||
| AhrefsKeywordOverviewResponse
|
||||
| AhrefsBrokenBacklinksResponse
|
||||
| AhrefsMetricsResponse
|
||||
| AhrefsOrganicCompetitorsResponse
|
||||
|
||||
@@ -72,6 +72,8 @@ import {
|
||||
ahrefsBrokenBacklinksTool,
|
||||
ahrefsDomainRatingTool,
|
||||
ahrefsKeywordOverviewTool,
|
||||
ahrefsMetricsTool,
|
||||
ahrefsOrganicCompetitorsTool,
|
||||
ahrefsOrganicKeywordsTool,
|
||||
ahrefsReferringDomainsTool,
|
||||
ahrefsTopPagesTool,
|
||||
@@ -6700,14 +6702,16 @@ export const tools: Record<string, ToolConfig> = {
|
||||
attio_update_record: attioUpdateRecordTool,
|
||||
attio_update_task: attioUpdateTaskTool,
|
||||
attio_update_webhook: attioUpdateWebhookTool,
|
||||
ahrefs_domain_rating: ahrefsDomainRatingTool,
|
||||
ahrefs_backlinks: ahrefsBacklinksTool,
|
||||
ahrefs_backlinks_stats: ahrefsBacklinksStatsTool,
|
||||
ahrefs_referring_domains: ahrefsReferringDomainsTool,
|
||||
ahrefs_organic_keywords: ahrefsOrganicKeywordsTool,
|
||||
ahrefs_top_pages: ahrefsTopPagesTool,
|
||||
ahrefs_keyword_overview: ahrefsKeywordOverviewTool,
|
||||
ahrefs_broken_backlinks: ahrefsBrokenBacklinksTool,
|
||||
ahrefs_domain_rating: ahrefsDomainRatingTool,
|
||||
ahrefs_keyword_overview: ahrefsKeywordOverviewTool,
|
||||
ahrefs_metrics: ahrefsMetricsTool,
|
||||
ahrefs_organic_competitors: ahrefsOrganicCompetitorsTool,
|
||||
ahrefs_organic_keywords: ahrefsOrganicKeywordsTool,
|
||||
ahrefs_referring_domains: ahrefsReferringDomainsTool,
|
||||
ahrefs_top_pages: ahrefsTopPagesTool,
|
||||
apify_run_actor_sync: apifyRunActorSyncTool,
|
||||
apify_run_actor_async: apifyRunActorAsyncTool,
|
||||
apify_run_task: apifyRunTaskTool,
|
||||
|
||||
Reference in New Issue
Block a user