feat(profound): add Profound AI visibility and analytics integration (#3849)

* feat(profound): add Profound AI visibility and analytics integration

* fix(profound): fix import ordering and JSON formatting for CI lint

* fix(profound): gate metrics mapping on current operation to prevent stale overrides

* fix(profound): guard JSON.parse on filters, fix offset=0 falsy check, remove duplicate prompt_answers in FILTER_OPS

* lint

* fix(docs): fix import ordering and trailing newline for docs lint

* fix(scripts): sort generated imports to match Biome's organizeImports order

* fix(profound): use != null checks for limit param across all tools

* fix(profound): flatten block output type to 'json' to pass block validation test

* fix(profound): remove invalid 'required' field from block inputs (not part of ParamConfig)

* fix(profound): rename tool files from kebab-case to snake_case for docs generator compatibility

* lint

* fix(docs): let biome auto-fix import order, revert custom sort in generator

* fix(landing): fix import order in sim icon-mapping via biome

* fix(scripts): match Biome's exact import sort order in docs generator

* fix(generate-docs): produce Biome-compatible JSON output

The generator wrote multi-line arrays for short string arrays (like tags)
and omitted trailing newlines, causing Biome format check failures in CI.
Post-process integrations.json to collapse short arrays onto single lines
and add trailing newlines to both integrations.json and meta.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-03-30 16:30:06 -07:00
committed by GitHub
parent 4ae5b1b620
commit e5aef6184a
37 changed files with 4356 additions and 5 deletions

View File

@@ -1285,6 +1285,17 @@ export function StartIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function ProfoundIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width='1em' height='1em' viewBox='0 0 55 55' xmlns='http://www.w3.org/2000/svg' {...props}>
<path
fill='currentColor'
d='M0 36.685V21.349a7.017 7.017 0 0 1 2.906-5.69l19.742-14.25A7.443 7.443 0 0 1 27.004 0h.062c1.623 0 3.193.508 4.501 1.452l19.684 14.207a7.016 7.016 0 0 1 2.906 5.69v12.302a7.013 7.013 0 0 1-2.907 5.689L31.527 53.562A7.605 7.605 0 0 1 27.078 55a7.641 7.641 0 0 1-4.465-1.44c-2.581-1.859-6.732-4.855-6.732-4.855V29.777c0-.249.28-.393.482-.248l10.538 7.605c.106.077.249.077.355 0l13.005-9.386a.306.306 0 0 0 0-.496l-13.005-9.386a.303.303 0 0 0-.355 0L.482 36.933A.304.304 0 0 1 0 36.685Z'
/>
</svg>
)
}
export function PineconeIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg

View File

@@ -126,6 +126,7 @@ import {
PolymarketIcon,
PostgresIcon,
PosthogIcon,
ProfoundIcon,
PulseIcon,
QdrantIcon,
QuiverIcon,
@@ -302,6 +303,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
polymarket: PolymarketIcon,
postgresql: PostgresIcon,
posthog: PosthogIcon,
profound: ProfoundIcon,
pulse_v2: PulseIcon,
qdrant: QdrantIcon,
quiver: QuiverIcon,

View File

@@ -121,6 +121,7 @@
"polymarket",
"postgresql",
"posthog",
"profound",
"pulse",
"qdrant",
"quiver",

View File

@@ -0,0 +1,626 @@
---
title: Profound
description: AI visibility and analytics with Profound
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="profound"
color="#1A1A2E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Profound](https://tryprofound.com/) is an AI visibility and analytics platform that helps brands understand how they appear across AI-powered search engines, chatbots, and assistants. It tracks mentions, citations, sentiment, bot traffic, and referral patterns across platforms like ChatGPT, Perplexity, Google AI Overviews, and more.
With the Profound integration in Sim, you can:
- **Monitor AI Visibility**: Track share of voice, visibility scores, and mention counts across AI platforms for your brand and competitors.
- **Analyze Sentiment**: Measure how positively or negatively your brand is discussed in AI-generated responses.
- **Track Citations**: See which URLs are being cited by AI models and your citation share relative to competitors.
- **Monitor Bot Traffic**: Analyze AI crawler activity on your domain, including GPTBot, ClaudeBot, and other AI agents, with hourly granularity.
- **Track Referral Traffic**: Monitor human visits arriving from AI platforms to your website.
- **Explore Prompt Data**: Access raw prompt-answer pairs, query fanouts, and prompt volume trends across AI platforms.
- **Optimize Content**: Get AEO (Answer Engine Optimization) scores and actionable recommendations to improve how AI models reference your content.
- **Manage Categories & Assets**: List and explore your tracked categories, assets (brands), topics, tags, personas, and regions.
These tools let your agents automate AI visibility monitoring, competitive intelligence, and content optimization workflows. To use the Profound integration, you'll need a Profound account with API access.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Track how your brand appears across AI platforms. Monitor visibility scores, sentiment, citations, bot traffic, referrals, content optimization, and prompt volumes with Profound.
## Tools
### `profound_list_categories`
List all organization categories in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `categories` | json | List of organization categories |
| ↳ `id` | string | Category ID |
| ↳ `name` | string | Category name |
### `profound_list_regions`
List all organization regions in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `regions` | json | List of organization regions |
| ↳ `id` | string | Region ID \(UUID\) |
| ↳ `name` | string | Region name |
### `profound_list_models`
List all AI models/platforms tracked in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `models` | json | List of AI models/platforms |
| ↳ `id` | string | Model ID \(UUID\) |
| ↳ `name` | string | Model/platform name |
### `profound_list_domains`
List all organization domains in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `domains` | json | List of organization domains |
| ↳ `id` | string | Domain ID \(UUID\) |
| ↳ `name` | string | Domain name |
| ↳ `createdAt` | string | When the domain was added |
### `profound_list_assets`
List all organization assets (companies/brands) across all categories in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `assets` | json | List of organization assets with category info |
| ↳ `id` | string | Asset ID |
| ↳ `name` | string | Asset/company name |
| ↳ `website` | string | Asset website URL |
| ↳ `alternateDomains` | json | Alternate domain names |
| ↳ `isOwned` | boolean | Whether this asset is owned by the organization |
| ↳ `createdAt` | string | When the asset was created |
| ↳ `logoUrl` | string | URL of the asset logo |
| ↳ `categoryId` | string | Category ID the asset belongs to |
| ↳ `categoryName` | string | Category name |
### `profound_list_personas`
List all organization personas across all categories in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `personas` | json | List of organization personas with profile details |
| ↳ `id` | string | Persona ID |
| ↳ `name` | string | Persona name |
| ↳ `categoryId` | string | Category ID |
| ↳ `categoryName` | string | Category name |
| ↳ `persona` | json | Persona profile with behavior, employment, and demographics |
### `profound_category_topics`
List topics for a specific category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `topics` | json | List of topics in the category |
| ↳ `id` | string | Topic ID \(UUID\) |
| ↳ `name` | string | Topic name |
### `profound_category_tags`
List tags for a specific category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tags` | json | List of tags in the category |
| ↳ `id` | string | Tag ID \(UUID\) |
| ↳ `name` | string | Tag name |
### `profound_category_prompts`
List prompts for a specific category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
| `limit` | number | No | Maximum number of results \(default 10000, max 10000\) |
| `cursor` | string | No | Pagination cursor from previous response |
| `orderDir` | string | No | Sort direction: asc or desc \(default desc\) |
| `promptType` | string | No | Comma-separated prompt types to filter: visibility, sentiment |
| `topicId` | string | No | Comma-separated topic IDs \(UUIDs\) to filter by |
| `tagId` | string | No | Comma-separated tag IDs \(UUIDs\) to filter by |
| `regionId` | string | No | Comma-separated region IDs \(UUIDs\) to filter by |
| `platformId` | string | No | Comma-separated platform IDs \(UUIDs\) to filter by |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of prompts |
| `nextCursor` | string | Cursor for next page of results |
| `prompts` | json | List of prompts |
| ↳ `id` | string | Prompt ID |
| ↳ `prompt` | string | Prompt text |
| ↳ `promptType` | string | Prompt type \(visibility or sentiment\) |
| ↳ `topicId` | string | Topic ID |
| ↳ `topicName` | string | Topic name |
| ↳ `tags` | json | Associated tags |
| ↳ `regions` | json | Associated regions |
| ↳ `platforms` | json | Associated platforms |
| ↳ `createdAt` | string | When the prompt was created |
### `profound_category_assets`
List assets (companies/brands) for a specific category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `assets` | json | List of assets in the category |
| ↳ `id` | string | Asset ID |
| ↳ `name` | string | Asset/company name |
| ↳ `website` | string | Website URL |
| ↳ `alternateDomains` | json | Alternate domain names |
| ↳ `isOwned` | boolean | Whether the asset is owned by the organization |
| ↳ `createdAt` | string | When the asset was created |
| ↳ `logoUrl` | string | URL of the asset logo |
### `profound_category_personas`
List personas for a specific category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `personas` | json | List of personas in the category |
| ↳ `id` | string | Persona ID |
| ↳ `name` | string | Persona name |
| ↳ `persona` | json | Persona profile with behavior, employment, and demographics |
### `profound_visibility_report`
Query AI visibility report for a category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | Yes | End date \(YYYY-MM-DD or ISO 8601\) |
| `metrics` | string | Yes | Comma-separated metrics: share_of_voice, mentions_count, visibility_score, executions, average_position |
| `dimensions` | string | No | Comma-separated dimensions: date, region, topic, model, asset_name, prompt, tag, persona |
| `dateInterval` | string | No | Date interval: hour, day, week, month, year |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"asset_name","operator":"is","value":"Company"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of rows in the report |
| `data` | json | Report data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values matching requested metrics order |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_sentiment_report`
Query sentiment report for a category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | Yes | End date \(YYYY-MM-DD or ISO 8601\) |
| `metrics` | string | Yes | Comma-separated metrics: positive, negative, occurrences |
| `dimensions` | string | No | Comma-separated dimensions: theme, date, region, topic, model, asset_name, tag, prompt, sentiment_type, persona |
| `dateInterval` | string | No | Date interval: hour, day, week, month, year |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"asset_name","operator":"is","value":"Company"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of rows in the report |
| `data` | json | Report data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values matching requested metrics order |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_citations_report`
Query citations report for a category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | Yes | End date \(YYYY-MM-DD or ISO 8601\) |
| `metrics` | string | Yes | Comma-separated metrics: count, citation_share |
| `dimensions` | string | No | Comma-separated dimensions: hostname, path, date, region, topic, model, tag, prompt, url, root_domain, persona, citation_category |
| `dateInterval` | string | No | Date interval: hour, day, week, month, year |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"hostname","operator":"is","value":"example.com"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of rows in the report |
| `data` | json | Report data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values matching requested metrics order |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_query_fanouts`
Query fanout report showing how AI models expand prompts into sub-queries in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | Yes | End date \(YYYY-MM-DD or ISO 8601\) |
| `metrics` | string | Yes | Comma-separated metrics: fanouts_per_execution, total_fanouts, share |
| `dimensions` | string | No | Comma-separated dimensions: prompt, query, model, region, date |
| `dateInterval` | string | No | Date interval: hour, day, week, month, year |
| `filters` | string | No | JSON array of filter objects |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of rows in the report |
| `data` | json | Report data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values matching requested metrics order |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_prompt_answers`
Get raw prompt answers data for a category in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `categoryId` | string | Yes | Category ID \(UUID\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | Yes | End date \(YYYY-MM-DD or ISO 8601\) |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"prompt_type","operator":"is","value":"visibility"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of answer rows |
| `data` | json | Raw prompt answer data |
| ↳ `prompt` | string | The prompt text |
| ↳ `promptType` | string | Prompt type \(visibility or sentiment\) |
| ↳ `response` | string | AI model response text |
| ↳ `mentions` | json | Companies/assets mentioned in the response |
| ↳ `citations` | json | URLs cited in the response |
| ↳ `topic` | string | Topic name |
| ↳ `region` | string | Region name |
| ↳ `model` | string | AI model/platform name |
| ↳ `asset` | string | Asset name |
| ↳ `createdAt` | string | Timestamp when the answer was collected |
### `profound_bots_report`
Query bot traffic report with hourly granularity for a domain in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `domain` | string | Yes | Domain to query bot traffic for \(e.g. example.com\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | No | End date \(YYYY-MM-DD or ISO 8601\). Defaults to now |
| `metrics` | string | Yes | Comma-separated metrics: count, citations, indexing, training, last_visit |
| `dimensions` | string | No | Comma-separated dimensions: date, hour, path, bot_name, bot_provider, bot_type |
| `dateInterval` | string | No | Date interval: hour, day, week, month, year |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"bot_name","operator":"is","value":"GPTBot"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of rows in the report |
| `data` | json | Report data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values matching requested metrics order |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_referrals_report`
Query human referral traffic report with hourly granularity for a domain in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `domain` | string | Yes | Domain to query referral traffic for \(e.g. example.com\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | No | End date \(YYYY-MM-DD or ISO 8601\). Defaults to now |
| `metrics` | string | Yes | Comma-separated metrics: visits, last_visit |
| `dimensions` | string | No | Comma-separated dimensions: date, hour, path, referral_source, referral_type |
| `dateInterval` | string | No | Date interval: hour, day, week, month, year |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"referral_source","operator":"is","value":"openai"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of rows in the report |
| `data` | json | Report data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values matching requested metrics order |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_raw_logs`
Get raw traffic logs with filters for a domain in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `domain` | string | Yes | Domain to query logs for \(e.g. example.com\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | No | End date \(YYYY-MM-DD or ISO 8601\). Defaults to now |
| `dimensions` | string | No | Comma-separated dimensions: timestamp, method, host, path, status_code, ip, user_agent, referer, bytes_sent, duration_ms, query_params |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"path","operator":"contains","value":"/blog"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of log entries |
| `data` | json | Log data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values \(count\) |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_bot_logs`
Get identified bot visit logs with filters for a domain in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `domain` | string | Yes | Domain to query bot logs for \(e.g. example.com\) |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | No | End date \(YYYY-MM-DD or ISO 8601\). Defaults to now |
| `dimensions` | string | No | Comma-separated dimensions: timestamp, method, host, path, status_code, ip, user_agent, referer, bytes_sent, duration_ms, query_params, bot_name, bot_provider, bot_types |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"bot_name","operator":"is","value":"GPTBot"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of bot log entries |
| `data` | json | Bot log data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values \(count\) |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_list_optimizations`
List content optimization entries for an asset in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `assetId` | string | Yes | Asset ID \(UUID\) |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
| `offset` | number | No | Offset for pagination \(default 0\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of optimization entries |
| `optimizations` | json | List of content optimization entries |
| ↳ `id` | string | Optimization ID \(UUID\) |
| ↳ `title` | string | Content title |
| ↳ `createdAt` | string | When the optimization was created |
| ↳ `extractedInput` | string | Extracted input text |
| ↳ `type` | string | Content type: file, text, or url |
| ↳ `status` | string | Optimization status |
### `profound_optimization_analysis`
Get detailed content optimization analysis for a specific content item in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `assetId` | string | Yes | Asset ID \(UUID\) |
| `contentId` | string | Yes | Content/optimization ID \(UUID\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | json | The analyzed content |
| ↳ `format` | string | Content format: markdown or html |
| ↳ `value` | string | Content text |
| `aeoContentScore` | json | AEO content score with target zone |
| ↳ `value` | number | AEO score value |
| ↳ `targetZone` | json | Target zone range |
| ↳ `low` | number | Low end of target range |
| ↳ `high` | number | High end of target range |
| `analysis` | json | Analysis breakdown by category |
| ↳ `breakdown` | json | Array of scoring breakdowns |
| ↳ `title` | string | Category title |
| ↳ `weight` | number | Category weight |
| ↳ `score` | number | Category score |
| `recommendations` | json | Content optimization recommendations |
| ↳ `title` | string | Recommendation title |
| ↳ `status` | string | Status: done or pending |
| ↳ `impact` | json | Impact details with section and score |
| ↳ `suggestion` | json | Suggestion text and rationale |
| ↳ `text` | string | Suggestion text |
| ↳ `rationale` | string | Why this recommendation matters |
### `profound_prompt_volume`
Query prompt volume data to understand search demand across AI platforms in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `startDate` | string | Yes | Start date \(YYYY-MM-DD or ISO 8601\) |
| `endDate` | string | Yes | End date \(YYYY-MM-DD or ISO 8601\) |
| `metrics` | string | Yes | Comma-separated metrics: volume, change |
| `dimensions` | string | No | Comma-separated dimensions: keyword, date, platform, country_code, matching_type, frequency |
| `dateInterval` | string | No | Date interval: hour, day, week, month, year |
| `filters` | string | No | JSON array of filter objects, e.g. \[\{"field":"keyword","operator":"contains","value":"best"\}\] |
| `limit` | number | No | Maximum number of results \(default 10000, max 50000\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `totalRows` | number | Total number of rows in the report |
| `data` | json | Volume data rows with metrics and dimension values |
| ↳ `metrics` | json | Array of metric values matching requested metrics order |
| ↳ `dimensions` | json | Array of dimension values matching requested dimensions order |
### `profound_citation_prompts`
Get prompts that cite a specific domain across AI platforms in Profound
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Profound API Key |
| `inputDomain` | string | Yes | Domain to look up citations for \(e.g. ramp.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | json | Citation prompt data for the queried domain |

View File

@@ -126,6 +126,7 @@ import {
PolymarketIcon,
PostgresIcon,
PosthogIcon,
ProfoundIcon,
PulseIcon,
QdrantIcon,
QuiverIcon,
@@ -302,6 +303,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
polymarket: PolymarketIcon,
postgresql: PostgresIcon,
posthog: PosthogIcon,
profound: ProfoundIcon,
pulse_v2: PulseIcon,
qdrant: QdrantIcon,
quiver: QuiverIcon,

View File

@@ -8611,6 +8611,121 @@
"integrationType": "analytics",
"tags": ["data-analytics", "monitoring"]
},
{
"type": "profound",
"slug": "profound",
"name": "Profound",
"description": "AI visibility and analytics with Profound",
"longDescription": "Track how your brand appears across AI platforms. Monitor visibility scores, sentiment, citations, bot traffic, referrals, content optimization, and prompt volumes with Profound.",
"bgColor": "#1A1A2E",
"iconName": "ProfoundIcon",
"docsUrl": "https://docs.sim.ai/tools/profound",
"operations": [
{
"name": "List Categories",
"description": "List all organization categories in Profound"
},
{
"name": "List Regions",
"description": "List all organization regions in Profound"
},
{
"name": "List Models",
"description": "List all AI models/platforms tracked in Profound"
},
{
"name": "List Domains",
"description": "List all organization domains in Profound"
},
{
"name": "List Assets",
"description": "List all organization assets (companies/brands) across all categories in Profound"
},
{
"name": "List Personas",
"description": "List all organization personas across all categories in Profound"
},
{
"name": "Category Topics",
"description": "List topics for a specific category in Profound"
},
{
"name": "Category Tags",
"description": "List tags for a specific category in Profound"
},
{
"name": "Category Prompts",
"description": "List prompts for a specific category in Profound"
},
{
"name": "Category Assets",
"description": "List assets (companies/brands) for a specific category in Profound"
},
{
"name": "Category Personas",
"description": "List personas for a specific category in Profound"
},
{
"name": "Visibility Report",
"description": "Query AI visibility report for a category in Profound"
},
{
"name": "Sentiment Report",
"description": "Query sentiment report for a category in Profound"
},
{
"name": "Citations Report",
"description": "Query citations report for a category in Profound"
},
{
"name": "Query Fanouts",
"description": "Query fanout report showing how AI models expand prompts into sub-queries in Profound"
},
{
"name": "Prompt Answers",
"description": "Get raw prompt answers data for a category in Profound"
},
{
"name": "Bots Report",
"description": "Query bot traffic report with hourly granularity for a domain in Profound"
},
{
"name": "Referrals Report",
"description": "Query human referral traffic report with hourly granularity for a domain in Profound"
},
{
"name": "Raw Logs",
"description": "Get raw traffic logs with filters for a domain in Profound"
},
{
"name": "Bot Logs",
"description": "Get identified bot visit logs with filters for a domain in Profound"
},
{
"name": "List Optimizations",
"description": "List content optimization entries for an asset in Profound"
},
{
"name": "Optimization Analysis",
"description": "Get detailed content optimization analysis for a specific content item in Profound"
},
{
"name": "Prompt Volume",
"description": "Query prompt volume data to understand search demand across AI platforms in Profound"
},
{
"name": "Citation Prompts",
"description": "Get prompts that cite a specific domain across AI platforms in Profound"
}
],
"operationCount": 24,
"triggers": [],
"triggerCount": 0,
"authType": "api-key",
"category": "tools",
"integrationType": "analytics",
"tags": ["seo", "data-analytics"]
},
{
"type": "pulse_v2",
"slug": "pulse",

View File

@@ -0,0 +1,406 @@
import { ProfoundIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
const CATEGORY_REPORT_OPS = [
'visibility_report',
'sentiment_report',
'citations_report',
'prompt_answers',
'query_fanouts',
] as const
const DOMAIN_REPORT_OPS = ['bots_report', 'referrals_report', 'raw_logs', 'bot_logs'] as const
const ALL_REPORT_OPS = [...CATEGORY_REPORT_OPS, ...DOMAIN_REPORT_OPS] as const
const CATEGORY_ID_OPS = [
...CATEGORY_REPORT_OPS,
'category_topics',
'category_tags',
'category_prompts',
'category_assets',
'category_personas',
] as const
const DATE_REQUIRED_CATEGORY_OPS = [
'visibility_report',
'sentiment_report',
'citations_report',
'prompt_answers',
'query_fanouts',
'prompt_volume',
] as const
const DATE_REQUIRED_ALL_OPS = [...DATE_REQUIRED_CATEGORY_OPS, ...DOMAIN_REPORT_OPS] as const
const METRICS_REPORT_OPS = [
'visibility_report',
'sentiment_report',
'citations_report',
'bots_report',
'referrals_report',
'query_fanouts',
'prompt_volume',
] as const
const DIMENSION_OPS = [
'visibility_report',
'sentiment_report',
'citations_report',
'bots_report',
'referrals_report',
'query_fanouts',
'raw_logs',
'bot_logs',
'prompt_volume',
] as const
const FILTER_OPS = [...ALL_REPORT_OPS, 'prompt_volume'] as const
export const ProfoundBlock: BlockConfig = {
type: 'profound',
name: 'Profound',
description: 'AI visibility and analytics with Profound',
longDescription:
'Track how your brand appears across AI platforms. Monitor visibility scores, sentiment, citations, bot traffic, referrals, content optimization, and prompt volumes with Profound.',
docsLink: 'https://docs.sim.ai/tools/profound',
category: 'tools',
integrationType: IntegrationType.Analytics,
tags: ['seo', 'data-analytics'],
bgColor: '#1A1A2E',
icon: ProfoundIcon,
authMode: AuthMode.ApiKey,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'List Categories', id: 'list_categories' },
{ label: 'List Regions', id: 'list_regions' },
{ label: 'List Models', id: 'list_models' },
{ label: 'List Domains', id: 'list_domains' },
{ label: 'List Assets', id: 'list_assets' },
{ label: 'List Personas', id: 'list_personas' },
{ label: 'Category Topics', id: 'category_topics' },
{ label: 'Category Tags', id: 'category_tags' },
{ label: 'Category Prompts', id: 'category_prompts' },
{ label: 'Category Assets', id: 'category_assets' },
{ label: 'Category Personas', id: 'category_personas' },
{ label: 'Visibility Report', id: 'visibility_report' },
{ label: 'Sentiment Report', id: 'sentiment_report' },
{ label: 'Citations Report', id: 'citations_report' },
{ label: 'Query Fanouts', id: 'query_fanouts' },
{ label: 'Prompt Answers', id: 'prompt_answers' },
{ label: 'Bots Report', id: 'bots_report' },
{ label: 'Referrals Report', id: 'referrals_report' },
{ label: 'Raw Logs', id: 'raw_logs' },
{ label: 'Bot Logs', id: 'bot_logs' },
{ label: 'List Optimizations', id: 'list_optimizations' },
{ label: 'Optimization Analysis', id: 'optimization_analysis' },
{ label: 'Prompt Volume', id: 'prompt_volume' },
{ label: 'Citation Prompts', id: 'citation_prompts' },
],
value: () => 'visibility_report',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your Profound API key',
required: true,
password: true,
},
// Category ID - for category-based operations
{
id: 'categoryId',
title: 'Category ID',
type: 'short-input',
placeholder: 'Category UUID',
required: { field: 'operation', value: [...CATEGORY_ID_OPS] },
condition: { field: 'operation', value: [...CATEGORY_ID_OPS] },
},
// Domain - for domain-based operations
{
id: 'domain',
title: 'Domain',
type: 'short-input',
placeholder: 'e.g. example.com',
required: { field: 'operation', value: [...DOMAIN_REPORT_OPS] },
condition: { field: 'operation', value: [...DOMAIN_REPORT_OPS] },
},
// Input domain - for citation prompts
{
id: 'inputDomain',
title: 'Domain',
type: 'short-input',
placeholder: 'e.g. ramp.com',
required: { field: 'operation', value: 'citation_prompts' },
condition: { field: 'operation', value: 'citation_prompts' },
},
// Asset ID - for content optimization
{
id: 'assetId',
title: 'Asset ID',
type: 'short-input',
placeholder: 'Asset UUID',
required: { field: 'operation', value: ['list_optimizations', 'optimization_analysis'] },
condition: { field: 'operation', value: ['list_optimizations', 'optimization_analysis'] },
},
// Content ID - for optimization analysis
{
id: 'contentId',
title: 'Content ID',
type: 'short-input',
placeholder: 'Content/optimization UUID',
required: { field: 'operation', value: 'optimization_analysis' },
condition: { field: 'operation', value: 'optimization_analysis' },
},
// Date fields
{
id: 'startDate',
title: 'Start Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
required: { field: 'operation', value: [...DATE_REQUIRED_ALL_OPS] },
condition: { field: 'operation', value: [...DATE_REQUIRED_ALL_OPS] },
wandConfig: {
enabled: true,
prompt: 'Generate a date in YYYY-MM-DD format. Return ONLY the date string.',
generationType: 'timestamp',
},
},
{
id: 'endDate',
title: 'End Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
required: { field: 'operation', value: [...DATE_REQUIRED_CATEGORY_OPS] },
condition: { field: 'operation', value: [...DATE_REQUIRED_ALL_OPS] },
wandConfig: {
enabled: true,
prompt: 'Generate a date in YYYY-MM-DD format. Return ONLY the date string.',
generationType: 'timestamp',
},
},
// Per-operation metrics fields
{
id: 'visibilityMetrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'share_of_voice, visibility_score, mentions_count',
required: { field: 'operation', value: 'visibility_report' },
condition: { field: 'operation', value: 'visibility_report' },
},
{
id: 'sentimentMetrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'positive, negative, occurrences',
required: { field: 'operation', value: 'sentiment_report' },
condition: { field: 'operation', value: 'sentiment_report' },
},
{
id: 'citationsMetrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'count, citation_share',
required: { field: 'operation', value: 'citations_report' },
condition: { field: 'operation', value: 'citations_report' },
},
{
id: 'botsMetrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'count, citations, indexing, training',
required: { field: 'operation', value: 'bots_report' },
condition: { field: 'operation', value: 'bots_report' },
},
{
id: 'referralsMetrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'visits, last_visit',
required: { field: 'operation', value: 'referrals_report' },
condition: { field: 'operation', value: 'referrals_report' },
},
{
id: 'fanoutsMetrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'fanouts_per_execution, total_fanouts, share',
required: { field: 'operation', value: 'query_fanouts' },
condition: { field: 'operation', value: 'query_fanouts' },
},
{
id: 'volumeMetrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'volume, change',
required: { field: 'operation', value: 'prompt_volume' },
condition: { field: 'operation', value: 'prompt_volume' },
},
// Advanced fields
{
id: 'dimensions',
title: 'Dimensions',
type: 'short-input',
placeholder: 'e.g. date, asset_name, model',
condition: { field: 'operation', value: [...DIMENSION_OPS] },
mode: 'advanced',
},
{
id: 'dateInterval',
title: 'Date Interval',
type: 'dropdown',
options: [
{ label: 'Day', id: 'day' },
{ label: 'Hour', id: 'hour' },
{ label: 'Week', id: 'week' },
{ label: 'Month', id: 'month' },
{ label: 'Year', id: 'year' },
],
condition: { field: 'operation', value: [...METRICS_REPORT_OPS] },
mode: 'advanced',
},
{
id: 'filters',
title: 'Filters',
type: 'long-input',
placeholder: '[{"field":"asset_name","operator":"is","value":"Company"}]',
condition: { field: 'operation', value: [...FILTER_OPS] },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate a JSON array of filter objects. Each object has "field", "operator", and "value" keys. Return ONLY valid JSON.',
generationType: 'json-object',
},
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '10000',
condition: {
field: 'operation',
value: [...FILTER_OPS, 'category_prompts', 'list_optimizations'],
},
mode: 'advanced',
},
// Category prompts specific fields
{
id: 'cursor',
title: 'Cursor',
type: 'short-input',
placeholder: 'Pagination cursor from previous response',
condition: { field: 'operation', value: 'category_prompts' },
mode: 'advanced',
},
{
id: 'promptType',
title: 'Prompt Type',
type: 'short-input',
placeholder: 'visibility, sentiment',
condition: { field: 'operation', value: 'category_prompts' },
mode: 'advanced',
},
// Optimization list specific
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'list_optimizations' },
mode: 'advanced',
},
],
tools: {
access: [
'profound_list_categories',
'profound_list_regions',
'profound_list_models',
'profound_list_domains',
'profound_list_assets',
'profound_list_personas',
'profound_category_topics',
'profound_category_tags',
'profound_category_prompts',
'profound_category_assets',
'profound_category_personas',
'profound_visibility_report',
'profound_sentiment_report',
'profound_citations_report',
'profound_query_fanouts',
'profound_prompt_answers',
'profound_bots_report',
'profound_referrals_report',
'profound_raw_logs',
'profound_bot_logs',
'profound_list_optimizations',
'profound_optimization_analysis',
'profound_prompt_volume',
'profound_citation_prompts',
],
config: {
tool: (params) => `profound_${params.operation}`,
params: (params) => {
const result: Record<string, unknown> = {}
const metricsMap: Record<string, string> = {
visibility_report: 'visibilityMetrics',
sentiment_report: 'sentimentMetrics',
citations_report: 'citationsMetrics',
bots_report: 'botsMetrics',
referrals_report: 'referralsMetrics',
query_fanouts: 'fanoutsMetrics',
prompt_volume: 'volumeMetrics',
}
const metricsField = metricsMap[params.operation as string]
if (metricsField && params[metricsField]) {
result.metrics = params[metricsField]
}
if (params.limit != null) result.limit = Number(params.limit)
if (params.offset != null) result.offset = Number(params.offset)
return result
},
},
},
inputs: {
apiKey: { type: 'string' },
categoryId: { type: 'string' },
domain: { type: 'string' },
inputDomain: { type: 'string' },
assetId: { type: 'string' },
contentId: { type: 'string' },
startDate: { type: 'string' },
endDate: { type: 'string' },
metrics: { type: 'string' },
dimensions: { type: 'string' },
dateInterval: { type: 'string' },
filters: { type: 'string' },
limit: { type: 'number' },
offset: { type: 'number' },
cursor: { type: 'string' },
promptType: { type: 'string' },
},
outputs: {
response: {
type: 'json',
},
},
}

View File

@@ -137,6 +137,7 @@ import { PipedriveBlock } from '@/blocks/blocks/pipedrive'
import { PolymarketBlock } from '@/blocks/blocks/polymarket'
import { PostgreSQLBlock } from '@/blocks/blocks/postgresql'
import { PostHogBlock } from '@/blocks/blocks/posthog'
import { ProfoundBlock } from '@/blocks/blocks/profound'
import { PulseBlock, PulseV2Block } from '@/blocks/blocks/pulse'
import { QdrantBlock } from '@/blocks/blocks/qdrant'
import { QuiverBlock } from '@/blocks/blocks/quiver'
@@ -357,6 +358,7 @@ export const registry: Record<string, BlockConfig> = {
perplexity: PerplexityBlock,
pinecone: PineconeBlock,
pipedrive: PipedriveBlock,
profound: ProfoundBlock,
polymarket: PolymarketBlock,
postgresql: PostgreSQLBlock,
posthog: PostHogBlock,

View File

@@ -1285,6 +1285,17 @@ export function StartIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function ProfoundIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width='1em' height='1em' viewBox='0 0 55 55' xmlns='http://www.w3.org/2000/svg' {...props}>
<path
fill='currentColor'
d='M0 36.685V21.349a7.017 7.017 0 0 1 2.906-5.69l19.742-14.25A7.443 7.443 0 0 1 27.004 0h.062c1.623 0 3.193.508 4.501 1.452l19.684 14.207a7.016 7.016 0 0 1 2.906 5.69v12.302a7.013 7.013 0 0 1-2.907 5.689L31.527 53.562A7.605 7.605 0 0 1 27.078 55a7.641 7.641 0 0 1-4.465-1.44c-2.581-1.859-6.732-4.855-6.732-4.855V29.777c0-.249.28-.393.482-.248l10.538 7.605c.106.077.249.077.355 0l13.005-9.386a.306.306 0 0 0 0-.496l-13.005-9.386a.303.303 0 0 0-.355 0L.482 36.933A.304.304 0 0 1 0 36.685Z'
/>
</svg>
)
}
export function PineconeIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg

View File

@@ -0,0 +1,137 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundBotLogsParams, ProfoundBotLogsResponse } from './types'
export const profoundBotLogsTool: ToolConfig<ProfoundBotLogsParams, ProfoundBotLogsResponse> = {
id: 'profound_bot_logs',
name: 'Profound Bot Logs',
description: 'Get identified bot visit logs with filters for a domain in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Domain to query bot logs for (e.g. example.com)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601). Defaults to now',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated dimensions: timestamp, method, host, path, status_code, ip, user_agent, referer, bytes_sent, duration_ms, query_params, bot_name, bot_provider, bot_types',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"bot_name","operator":"is","value":"GPTBot"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/logs/raw/bots',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
domain: params.domain,
start_date: params.startDate,
metrics: ['count'],
}
if (params.endDate) {
body.end_date = params.endDate
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to get bot logs')
}
if (Array.isArray(data)) {
return {
success: true,
output: {
totalRows: data.length,
data: data.map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of bot log entries',
},
data: {
type: 'json',
description: 'Bot log data rows with metrics and dimension values',
properties: {
metrics: { type: 'json', description: 'Array of metric values (count)' },
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,145 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundBotsReportParams, ProfoundBotsReportResponse } from './types'
export const profoundBotsReportTool: ToolConfig<
ProfoundBotsReportParams,
ProfoundBotsReportResponse
> = {
id: 'profound_bots_report',
name: 'Profound Bots Report',
description: 'Query bot traffic report with hourly granularity for a domain in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Domain to query bot traffic for (e.g. example.com)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601). Defaults to now',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated metrics: count, citations, indexing, training, last_visit',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated dimensions: date, hour, path, bot_name, bot_provider, bot_type',
},
dateInterval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date interval: hour, day, week, month, year',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"bot_name","operator":"is","value":"GPTBot"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v2/reports/bots',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
domain: params.domain,
start_date: params.startDate,
metrics: params.metrics.split(',').map((m) => m.trim()),
}
if (params.endDate) {
body.end_date = params.endDate
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.dateInterval) {
body.date_interval = params.dateInterval
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to query bots report')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of rows in the report',
},
data: {
type: 'json',
description: 'Report data rows with metrics and dimension values',
properties: {
metrics: {
type: 'json',
description: 'Array of metric values matching requested metrics order',
},
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,84 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundCategoryAssetsParams, ProfoundCategoryAssetsResponse } from './types'
export const profoundCategoryAssetsTool: ToolConfig<
ProfoundCategoryAssetsParams,
ProfoundCategoryAssetsResponse
> = {
id: 'profound_category_assets',
name: 'Profound Category Assets',
description: 'List assets (companies/brands) for a specific category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
},
request: {
url: (params) =>
`https://api.tryprofound.com/v1/org/categories/${encodeURIComponent(params.categoryId)}/assets`,
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list category assets')
}
return {
success: true,
output: {
assets: (data ?? []).map(
(item: {
id: string
name: string
website: string
alternate_domains: string[] | null
is_owned: boolean
created_at: string
logo_url: string
}) => ({
id: item.id ?? null,
name: item.name ?? null,
website: item.website ?? null,
alternateDomains: item.alternate_domains ?? null,
isOwned: item.is_owned ?? false,
createdAt: item.created_at ?? null,
logoUrl: item.logo_url ?? null,
})
),
},
}
},
outputs: {
assets: {
type: 'json',
description: 'List of assets in the category',
properties: {
id: { type: 'string', description: 'Asset ID' },
name: { type: 'string', description: 'Asset/company name' },
website: { type: 'string', description: 'Website URL' },
alternateDomains: { type: 'json', description: 'Alternate domain names' },
isOwned: { type: 'boolean', description: 'Whether the asset is owned by the organization' },
createdAt: { type: 'string', description: 'When the asset was created' },
logoUrl: { type: 'string', description: 'URL of the asset logo' },
},
},
},
}

View File

@@ -0,0 +1,98 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundCategoryPersonasParams, ProfoundCategoryPersonasResponse } from './types'
export const profoundCategoryPersonasTool: ToolConfig<
ProfoundCategoryPersonasParams,
ProfoundCategoryPersonasResponse
> = {
id: 'profound_category_personas',
name: 'Profound Category Personas',
description: 'List personas for a specific category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
},
request: {
url: (params) =>
`https://api.tryprofound.com/v1/org/categories/${encodeURIComponent(params.categoryId)}/personas`,
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list category personas')
}
return {
success: true,
output: {
personas: (data.data ?? []).map(
(item: {
id: string
name: string
persona: {
behavior: { painPoints: string | null; motivations: string | null }
employment: {
industry: string[]
jobTitle: string[]
companySize: string[]
roleSeniority: string[]
}
demographics: { ageRange: string[] }
}
}) => ({
id: item.id ?? null,
name: item.name ?? null,
persona: {
behavior: {
painPoints: item.persona?.behavior?.painPoints ?? null,
motivations: item.persona?.behavior?.motivations ?? null,
},
employment: {
industry: item.persona?.employment?.industry ?? [],
jobTitle: item.persona?.employment?.jobTitle ?? [],
companySize: item.persona?.employment?.companySize ?? [],
roleSeniority: item.persona?.employment?.roleSeniority ?? [],
},
demographics: {
ageRange: item.persona?.demographics?.ageRange ?? [],
},
},
})
),
},
}
},
outputs: {
personas: {
type: 'json',
description: 'List of personas in the category',
properties: {
id: { type: 'string', description: 'Persona ID' },
name: { type: 'string', description: 'Persona name' },
persona: {
type: 'json',
description: 'Persona profile with behavior, employment, and demographics',
},
},
},
},
}

View File

@@ -0,0 +1,189 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundCategoryPromptsParams, ProfoundCategoryPromptsResponse } from './types'
export const profoundCategoryPromptsTool: ToolConfig<
ProfoundCategoryPromptsParams,
ProfoundCategoryPromptsResponse
> = {
id: 'profound_category_prompts',
name: 'Profound Category Prompts',
description: 'List prompts for a specific category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 10000)',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from previous response',
},
orderDir: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction: asc or desc (default desc)',
},
promptType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated prompt types to filter: visibility, sentiment',
},
topicId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated topic IDs (UUIDs) to filter by',
},
tagId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated tag IDs (UUIDs) to filter by',
},
regionId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated region IDs (UUIDs) to filter by',
},
platformId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated platform IDs (UUIDs) to filter by',
},
},
request: {
url: (params) => {
const url = new URL(
`https://api.tryprofound.com/v1/org/categories/${encodeURIComponent(params.categoryId)}/prompts`
)
if (params.limit != null) url.searchParams.set('limit', String(params.limit))
if (params.cursor) url.searchParams.set('cursor', params.cursor)
if (params.orderDir) url.searchParams.set('order_dir', params.orderDir)
if (params.promptType) {
for (const pt of params.promptType.split(',').map((s) => s.trim())) {
url.searchParams.append('prompt_type', pt)
}
}
if (params.topicId) {
for (const tid of params.topicId.split(',').map((s) => s.trim())) {
url.searchParams.append('topic_id', tid)
}
}
if (params.tagId) {
for (const tid of params.tagId.split(',').map((s) => s.trim())) {
url.searchParams.append('tag_id', tid)
}
}
if (params.regionId) {
for (const rid of params.regionId.split(',').map((s) => s.trim())) {
url.searchParams.append('region_id', rid)
}
}
if (params.platformId) {
for (const pid of params.platformId.split(',').map((s) => s.trim())) {
url.searchParams.append('platform_id', pid)
}
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list category prompts')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
nextCursor: data.info?.next_cursor ?? null,
prompts: (data.data ?? []).map(
(item: {
id: string
prompt: string
prompt_type: string
topic: { id: string; name: string }
tags: Array<{ id: string; name: string }>
regions: Array<{ id: string; name: string }>
platforms: Array<{ id: string; name: string }>
created_at: string
}) => ({
id: item.id ?? null,
prompt: item.prompt ?? null,
promptType: item.prompt_type ?? null,
topicId: item.topic?.id ?? null,
topicName: item.topic?.name ?? null,
tags: (item.tags ?? []).map((t: { id: string; name: string }) => ({
id: t.id ?? null,
name: t.name ?? null,
})),
regions: (item.regions ?? []).map((r: { id: string; name: string }) => ({
id: r.id ?? null,
name: r.name ?? null,
})),
platforms: (item.platforms ?? []).map((p: { id: string; name: string }) => ({
id: p.id ?? null,
name: p.name ?? null,
})),
createdAt: item.created_at ?? null,
})
),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of prompts',
},
nextCursor: {
type: 'string',
description: 'Cursor for next page of results',
optional: true,
},
prompts: {
type: 'json',
description: 'List of prompts',
properties: {
id: { type: 'string', description: 'Prompt ID' },
prompt: { type: 'string', description: 'Prompt text' },
promptType: { type: 'string', description: 'Prompt type (visibility or sentiment)' },
topicId: { type: 'string', description: 'Topic ID' },
topicName: { type: 'string', description: 'Topic name' },
tags: { type: 'json', description: 'Associated tags' },
regions: { type: 'json', description: 'Associated regions' },
platforms: { type: 'json', description: 'Associated platforms' },
createdAt: { type: 'string', description: 'When the prompt was created' },
},
},
},
}

View File

@@ -0,0 +1,64 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundCategoryTagsParams, ProfoundCategoryTagsResponse } from './types'
export const profoundCategoryTagsTool: ToolConfig<
ProfoundCategoryTagsParams,
ProfoundCategoryTagsResponse
> = {
id: 'profound_category_tags',
name: 'Profound Category Tags',
description: 'List tags for a specific category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
},
request: {
url: (params) =>
`https://api.tryprofound.com/v1/org/categories/${encodeURIComponent(params.categoryId)}/tags`,
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list category tags')
}
return {
success: true,
output: {
tags: (data ?? []).map((item: { id: string; name: string }) => ({
id: item.id ?? null,
name: item.name ?? null,
})),
},
}
},
outputs: {
tags: {
type: 'json',
description: 'List of tags in the category',
properties: {
id: { type: 'string', description: 'Tag ID (UUID)' },
name: { type: 'string', description: 'Tag name' },
},
},
},
}

View File

@@ -0,0 +1,64 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundCategoryTopicsParams, ProfoundCategoryTopicsResponse } from './types'
export const profoundCategoryTopicsTool: ToolConfig<
ProfoundCategoryTopicsParams,
ProfoundCategoryTopicsResponse
> = {
id: 'profound_category_topics',
name: 'Profound Category Topics',
description: 'List topics for a specific category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
},
request: {
url: (params) =>
`https://api.tryprofound.com/v1/org/categories/${encodeURIComponent(params.categoryId)}/topics`,
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list category topics')
}
return {
success: true,
output: {
topics: (data ?? []).map((item: { id: string; name: string }) => ({
id: item.id ?? null,
name: item.name ?? null,
})),
},
}
},
outputs: {
topics: {
type: 'json',
description: 'List of topics in the category',
properties: {
id: { type: 'string', description: 'Topic ID (UUID)' },
name: { type: 'string', description: 'Topic name' },
},
},
},
}

View File

@@ -0,0 +1,60 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundCitationPromptsParams, ProfoundCitationPromptsResponse } from './types'
export const profoundCitationPromptsTool: ToolConfig<
ProfoundCitationPromptsParams,
ProfoundCitationPromptsResponse
> = {
id: 'profound_citation_prompts',
name: 'Profound Citation Prompts',
description: 'Get prompts that cite a specific domain across AI platforms in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
inputDomain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Domain to look up citations for (e.g. ramp.com)',
},
},
request: {
url: (params) => {
const url = new URL('https://api.tryprofound.com/v1/prompt-volumes/citation-prompts')
url.searchParams.set('input_domain', params.inputDomain)
return url.toString()
},
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to get citation prompts')
}
return {
success: true,
output: {
data: data ?? null,
},
}
},
outputs: {
data: {
type: 'json',
description: 'Citation prompt data for the queried domain',
},
},
}

View File

@@ -0,0 +1,144 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundCitationsReportParams, ProfoundCitationsReportResponse } from './types'
export const profoundCitationsReportTool: ToolConfig<
ProfoundCitationsReportParams,
ProfoundCitationsReportResponse
> = {
id: 'profound_citations_report',
name: 'Profound Citations Report',
description: 'Query citations report for a category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601)',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated metrics: count, citation_share',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated dimensions: hostname, path, date, region, topic, model, tag, prompt, url, root_domain, persona, citation_category',
},
dateInterval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date interval: hour, day, week, month, year',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"hostname","operator":"is","value":"example.com"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/reports/citations',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
category_id: params.categoryId,
start_date: params.startDate,
end_date: params.endDate,
metrics: params.metrics.split(',').map((m) => m.trim()),
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.dateInterval) {
body.date_interval = params.dateInterval
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to query citations report')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of rows in the report',
},
data: {
type: 'json',
description: 'Report data rows with metrics and dimension values',
properties: {
metrics: {
type: 'json',
description: 'Array of metric values matching requested metrics order',
},
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,24 @@
export { profoundBotLogsTool } from './bot_logs'
export { profoundBotsReportTool } from './bots_report'
export { profoundCategoryAssetsTool } from './category_assets'
export { profoundCategoryPersonasTool } from './category_personas'
export { profoundCategoryPromptsTool } from './category_prompts'
export { profoundCategoryTagsTool } from './category_tags'
export { profoundCategoryTopicsTool } from './category_topics'
export { profoundCitationPromptsTool } from './citation_prompts'
export { profoundCitationsReportTool } from './citations_report'
export { profoundListAssetsTool } from './list_assets'
export { profoundListCategoriesTool } from './list_categories'
export { profoundListDomainsTool } from './list_domains'
export { profoundListModelsTool } from './list_models'
export { profoundListOptimizationsTool } from './list_optimizations'
export { profoundListPersonasTool } from './list_personas'
export { profoundListRegionsTool } from './list_regions'
export { profoundOptimizationAnalysisTool } from './optimization_analysis'
export { profoundPromptAnswersTool } from './prompt_answers'
export { profoundPromptVolumeTool } from './prompt_volume'
export { profoundQueryFanoutsTool } from './query_fanouts'
export { profoundRawLogsTool } from './raw_logs'
export { profoundReferralsReportTool } from './referrals_report'
export { profoundSentimentReportTool } from './sentiment_report'
export { profoundVisibilityReportTool } from './visibility_report'

View File

@@ -0,0 +1,85 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundListAssetsParams, ProfoundListAssetsResponse } from './types'
export const profoundListAssetsTool: ToolConfig<
ProfoundListAssetsParams,
ProfoundListAssetsResponse
> = {
id: 'profound_list_assets',
name: 'Profound List Assets',
description: 'List all organization assets (companies/brands) across all categories in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
},
request: {
url: 'https://api.tryprofound.com/v1/org/assets',
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list assets')
}
return {
success: true,
output: {
assets: (data.data ?? []).map(
(item: {
id: string
name: string
website: string
alternate_domains: string[] | null
is_owned: boolean
created_at: string
logo_url: string
category: { id: string; name: string }
}) => ({
id: item.id ?? null,
name: item.name ?? null,
website: item.website ?? null,
alternateDomains: item.alternate_domains ?? null,
isOwned: item.is_owned ?? false,
createdAt: item.created_at ?? null,
logoUrl: item.logo_url ?? null,
categoryId: item.category?.id ?? null,
categoryName: item.category?.name ?? null,
})
),
},
}
},
outputs: {
assets: {
type: 'json',
description: 'List of organization assets with category info',
properties: {
id: { type: 'string', description: 'Asset ID' },
name: { type: 'string', description: 'Asset/company name' },
website: { type: 'string', description: 'Asset website URL' },
alternateDomains: { type: 'json', description: 'Alternate domain names' },
isOwned: {
type: 'boolean',
description: 'Whether this asset is owned by the organization',
},
createdAt: { type: 'string', description: 'When the asset was created' },
logoUrl: { type: 'string', description: 'URL of the asset logo' },
categoryId: { type: 'string', description: 'Category ID the asset belongs to' },
categoryName: { type: 'string', description: 'Category name' },
},
},
},
}

View File

@@ -0,0 +1,57 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundListCategoriesParams, ProfoundListCategoriesResponse } from './types'
export const profoundListCategoriesTool: ToolConfig<
ProfoundListCategoriesParams,
ProfoundListCategoriesResponse
> = {
id: 'profound_list_categories',
name: 'Profound List Categories',
description: 'List all organization categories in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
},
request: {
url: 'https://api.tryprofound.com/v1/org/categories',
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list categories')
}
return {
success: true,
output: {
categories: (data ?? []).map((item: { id: string; name: string }) => ({
id: item.id ?? null,
name: item.name ?? null,
})),
},
}
},
outputs: {
categories: {
type: 'json',
description: 'List of organization categories',
properties: {
id: { type: 'string', description: 'Category ID' },
name: { type: 'string', description: 'Category name' },
},
},
},
}

View File

@@ -0,0 +1,59 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundListDomainsParams, ProfoundListDomainsResponse } from './types'
export const profoundListDomainsTool: ToolConfig<
ProfoundListDomainsParams,
ProfoundListDomainsResponse
> = {
id: 'profound_list_domains',
name: 'Profound List Domains',
description: 'List all organization domains in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
},
request: {
url: 'https://api.tryprofound.com/v1/org/domains',
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list domains')
}
return {
success: true,
output: {
domains: (data ?? []).map((item: { id: string; name: string; created_at: string }) => ({
id: item.id ?? null,
name: item.name ?? null,
createdAt: item.created_at ?? null,
})),
},
}
},
outputs: {
domains: {
type: 'json',
description: 'List of organization domains',
properties: {
id: { type: 'string', description: 'Domain ID (UUID)' },
name: { type: 'string', description: 'Domain name' },
createdAt: { type: 'string', description: 'When the domain was added' },
},
},
},
}

View File

@@ -0,0 +1,57 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundListModelsParams, ProfoundListModelsResponse } from './types'
export const profoundListModelsTool: ToolConfig<
ProfoundListModelsParams,
ProfoundListModelsResponse
> = {
id: 'profound_list_models',
name: 'Profound List Models',
description: 'List all AI models/platforms tracked in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
},
request: {
url: 'https://api.tryprofound.com/v1/org/models',
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list models')
}
return {
success: true,
output: {
models: (data ?? []).map((item: { id: string; name: string }) => ({
id: item.id ?? null,
name: item.name ?? null,
})),
},
}
},
outputs: {
models: {
type: 'json',
description: 'List of AI models/platforms',
properties: {
id: { type: 'string', description: 'Model ID (UUID)' },
name: { type: 'string', description: 'Model/platform name' },
},
},
},
}

View File

@@ -0,0 +1,104 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundListOptimizationsParams, ProfoundListOptimizationsResponse } from './types'
export const profoundListOptimizationsTool: ToolConfig<
ProfoundListOptimizationsParams,
ProfoundListOptimizationsResponse
> = {
id: 'profound_list_optimizations',
name: 'Profound List Optimizations',
description: 'List content optimization entries for an asset in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
assetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Asset ID (UUID)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Offset for pagination (default 0)',
},
},
request: {
url: (params) => {
const url = new URL(
`https://api.tryprofound.com/v1/content/${encodeURIComponent(params.assetId)}/optimization`
)
if (params.limit != null) url.searchParams.set('limit', String(params.limit))
if (params.offset != null) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list optimizations')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
optimizations: (data.data ?? []).map(
(item: {
id: string
title: string
created_at: string
extracted_input: string | null
type: string
status: string
}) => ({
id: item.id ?? null,
title: item.title ?? null,
createdAt: item.created_at ?? null,
extractedInput: item.extracted_input ?? null,
type: item.type ?? null,
status: item.status ?? null,
})
),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of optimization entries',
},
optimizations: {
type: 'json',
description: 'List of content optimization entries',
properties: {
id: { type: 'string', description: 'Optimization ID (UUID)' },
title: { type: 'string', description: 'Content title' },
createdAt: { type: 'string', description: 'When the optimization was created' },
extractedInput: { type: 'string', description: 'Extracted input text' },
type: { type: 'string', description: 'Content type: file, text, or url' },
status: { type: 'string', description: 'Optimization status' },
},
},
},
}

View File

@@ -0,0 +1,96 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundListPersonasParams, ProfoundListPersonasResponse } from './types'
export const profoundListPersonasTool: ToolConfig<
ProfoundListPersonasParams,
ProfoundListPersonasResponse
> = {
id: 'profound_list_personas',
name: 'Profound List Personas',
description: 'List all organization personas across all categories in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
},
request: {
url: 'https://api.tryprofound.com/v1/org/personas',
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list personas')
}
return {
success: true,
output: {
personas: (data.data ?? []).map(
(item: {
id: string
name: string
category: { id: string; name: string }
persona: {
behavior: { painPoints: string | null; motivations: string | null }
employment: {
industry: string[]
jobTitle: string[]
companySize: string[]
roleSeniority: string[]
}
demographics: { ageRange: string[] }
}
}) => ({
id: item.id ?? null,
name: item.name ?? null,
categoryId: item.category?.id ?? null,
categoryName: item.category?.name ?? null,
persona: {
behavior: {
painPoints: item.persona?.behavior?.painPoints ?? null,
motivations: item.persona?.behavior?.motivations ?? null,
},
employment: {
industry: item.persona?.employment?.industry ?? [],
jobTitle: item.persona?.employment?.jobTitle ?? [],
companySize: item.persona?.employment?.companySize ?? [],
roleSeniority: item.persona?.employment?.roleSeniority ?? [],
},
demographics: {
ageRange: item.persona?.demographics?.ageRange ?? [],
},
},
})
),
},
}
},
outputs: {
personas: {
type: 'json',
description: 'List of organization personas with profile details',
properties: {
id: { type: 'string', description: 'Persona ID' },
name: { type: 'string', description: 'Persona name' },
categoryId: { type: 'string', description: 'Category ID' },
categoryName: { type: 'string', description: 'Category name' },
persona: {
type: 'json',
description: 'Persona profile with behavior, employment, and demographics',
},
},
},
},
}

View File

@@ -0,0 +1,57 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundListRegionsParams, ProfoundListRegionsResponse } from './types'
export const profoundListRegionsTool: ToolConfig<
ProfoundListRegionsParams,
ProfoundListRegionsResponse
> = {
id: 'profound_list_regions',
name: 'Profound List Regions',
description: 'List all organization regions in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
},
request: {
url: 'https://api.tryprofound.com/v1/org/regions',
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to list regions')
}
return {
success: true,
output: {
regions: (data ?? []).map((item: { id: string; name: string }) => ({
id: item.id ?? null,
name: item.name ?? null,
})),
},
}
},
outputs: {
regions: {
type: 'json',
description: 'List of organization regions',
properties: {
id: { type: 'string', description: 'Region ID (UUID)' },
name: { type: 'string', description: 'Region name' },
},
},
},
}

View File

@@ -0,0 +1,161 @@
import type { ToolConfig } from '@/tools/types'
import type {
ProfoundOptimizationAnalysisParams,
ProfoundOptimizationAnalysisResponse,
} from './types'
export const profoundOptimizationAnalysisTool: ToolConfig<
ProfoundOptimizationAnalysisParams,
ProfoundOptimizationAnalysisResponse
> = {
id: 'profound_optimization_analysis',
name: 'Profound Optimization Analysis',
description: 'Get detailed content optimization analysis for a specific content item in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
assetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Asset ID (UUID)',
},
contentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Content/optimization ID (UUID)',
},
},
request: {
url: (params) =>
`https://api.tryprofound.com/v1/content/${encodeURIComponent(params.assetId)}/optimization/${encodeURIComponent(params.contentId)}`,
method: 'GET',
headers: (params) => ({
'X-API-Key': params.apiKey,
Accept: 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to get optimization analysis')
}
const analysis = data.data
return {
success: true,
output: {
content: {
format: analysis?.content?.format ?? null,
value: analysis?.content?.value ?? null,
},
aeoContentScore: analysis?.aeo_content_score
? {
value: analysis.aeo_content_score.value ?? 0,
targetZone: {
low: analysis.aeo_content_score.target_zone?.low ?? 0,
high: analysis.aeo_content_score.target_zone?.high ?? 0,
},
}
: null,
analysis: {
breakdown: (analysis?.analysis?.breakdown ?? []).map(
(b: { title: string; weight: number; score: number }) => ({
title: b.title ?? null,
weight: b.weight ?? 0,
score: b.score ?? 0,
})
),
},
recommendations: (analysis?.recommendations ?? []).map(
(r: {
title: string
status: string
impact: { section: string; score: number } | null
suggestion: { text: string; rationale: string }
}) => ({
title: r.title ?? null,
status: r.status ?? null,
impact: r.impact
? {
section: r.impact.section ?? null,
score: r.impact.score ?? 0,
}
: null,
suggestion: {
text: r.suggestion?.text ?? null,
rationale: r.suggestion?.rationale ?? null,
},
})
),
},
}
},
outputs: {
content: {
type: 'json',
description: 'The analyzed content',
properties: {
format: { type: 'string', description: 'Content format: markdown or html' },
value: { type: 'string', description: 'Content text' },
},
},
aeoContentScore: {
type: 'json',
description: 'AEO content score with target zone',
optional: true,
properties: {
value: { type: 'number', description: 'AEO score value' },
targetZone: {
type: 'json',
description: 'Target zone range',
properties: {
low: { type: 'number', description: 'Low end of target range' },
high: { type: 'number', description: 'High end of target range' },
},
},
},
},
analysis: {
type: 'json',
description: 'Analysis breakdown by category',
properties: {
breakdown: {
type: 'json',
description: 'Array of scoring breakdowns',
properties: {
title: { type: 'string', description: 'Category title' },
weight: { type: 'number', description: 'Category weight' },
score: { type: 'number', description: 'Category score' },
},
},
},
},
recommendations: {
type: 'json',
description: 'Content optimization recommendations',
properties: {
title: { type: 'string', description: 'Recommendation title' },
status: { type: 'string', description: 'Status: done or pending' },
impact: { type: 'json', description: 'Impact details with section and score' },
suggestion: {
type: 'json',
description: 'Suggestion text and rationale',
properties: {
text: { type: 'string', description: 'Suggestion text' },
rationale: { type: 'string', description: 'Why this recommendation matters' },
},
},
},
},
},
}

View File

@@ -0,0 +1,141 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundPromptAnswersParams, ProfoundPromptAnswersResponse } from './types'
export const profoundPromptAnswersTool: ToolConfig<
ProfoundPromptAnswersParams,
ProfoundPromptAnswersResponse
> = {
id: 'profound_prompt_answers',
name: 'Profound Prompt Answers',
description: 'Get raw prompt answers data for a category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601)',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"prompt_type","operator":"is","value":"visibility"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/prompts/answers',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
category_id: params.categoryId,
start_date: params.startDate,
end_date: params.endDate,
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to get prompt answers')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map(
(row: {
prompt: string | null
prompt_type: string | null
response: string | null
mentions: string[] | null
citations: string[] | null
topic: string | null
region: string | null
model: string | null
asset: string | null
created_at: string | null
}) => ({
prompt: row.prompt ?? null,
promptType: row.prompt_type ?? null,
response: row.response ?? null,
mentions: row.mentions ?? [],
citations: row.citations ?? [],
topic: row.topic ?? null,
region: row.region ?? null,
model: row.model ?? null,
asset: row.asset ?? null,
createdAt: row.created_at ?? null,
})
),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of answer rows',
},
data: {
type: 'json',
description: 'Raw prompt answer data',
properties: {
prompt: { type: 'string', description: 'The prompt text' },
promptType: { type: 'string', description: 'Prompt type (visibility or sentiment)' },
response: { type: 'string', description: 'AI model response text' },
mentions: { type: 'json', description: 'Companies/assets mentioned in the response' },
citations: { type: 'json', description: 'URLs cited in the response' },
topic: { type: 'string', description: 'Topic name' },
region: { type: 'string', description: 'Region name' },
model: { type: 'string', description: 'AI model/platform name' },
asset: { type: 'string', description: 'Asset name' },
createdAt: { type: 'string', description: 'Timestamp when the answer was collected' },
},
},
},
}

View File

@@ -0,0 +1,138 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundPromptVolumeParams, ProfoundPromptVolumeResponse } from './types'
export const profoundPromptVolumeTool: ToolConfig<
ProfoundPromptVolumeParams,
ProfoundPromptVolumeResponse
> = {
id: 'profound_prompt_volume',
name: 'Profound Prompt Volume',
description:
'Query prompt volume data to understand search demand across AI platforms in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601)',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated metrics: volume, change',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated dimensions: keyword, date, platform, country_code, matching_type, frequency',
},
dateInterval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date interval: hour, day, week, month, year',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"keyword","operator":"contains","value":"best"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/prompt-volumes/volume',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
start_date: params.startDate,
end_date: params.endDate,
metrics: params.metrics.split(',').map((m) => m.trim()),
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.dateInterval) {
body.date_interval = params.dateInterval
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to query prompt volume')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of rows in the report',
},
data: {
type: 'json',
description: 'Volume data rows with metrics and dimension values',
properties: {
metrics: {
type: 'json',
description: 'Array of metric values matching requested metrics order',
},
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,143 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundQueryFanoutsParams, ProfoundQueryFanoutsResponse } from './types'
export const profoundQueryFanoutsTool: ToolConfig<
ProfoundQueryFanoutsParams,
ProfoundQueryFanoutsResponse
> = {
id: 'profound_query_fanouts',
name: 'Profound Query Fanouts',
description:
'Query fanout report showing how AI models expand prompts into sub-queries in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601)',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated metrics: fanouts_per_execution, total_fanouts, share',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated dimensions: prompt, query, model, region, date',
},
dateInterval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date interval: hour, day, week, month, year',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON array of filter objects',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/reports/query-fanouts',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
category_id: params.categoryId,
start_date: params.startDate,
end_date: params.endDate,
metrics: params.metrics.split(',').map((m) => m.trim()),
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.dateInterval) {
body.date_interval = params.dateInterval
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to query fanouts report')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of rows in the report',
},
data: {
type: 'json',
description: 'Report data rows with metrics and dimension values',
properties: {
metrics: {
type: 'json',
description: 'Array of metric values matching requested metrics order',
},
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,137 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundRawLogsParams, ProfoundRawLogsResponse } from './types'
export const profoundRawLogsTool: ToolConfig<ProfoundRawLogsParams, ProfoundRawLogsResponse> = {
id: 'profound_raw_logs',
name: 'Profound Raw Logs',
description: 'Get raw traffic logs with filters for a domain in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Domain to query logs for (e.g. example.com)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601). Defaults to now',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated dimensions: timestamp, method, host, path, status_code, ip, user_agent, referer, bytes_sent, duration_ms, query_params',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"path","operator":"contains","value":"/blog"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/logs/raw',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
domain: params.domain,
start_date: params.startDate,
metrics: ['count'],
}
if (params.endDate) {
body.end_date = params.endDate
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to get raw logs')
}
if (Array.isArray(data)) {
return {
success: true,
output: {
totalRows: data.length,
data: data.map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of log entries',
},
data: {
type: 'json',
description: 'Log data rows with metrics and dimension values',
properties: {
metrics: { type: 'json', description: 'Array of metric values (count)' },
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,146 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundReferralsReportParams, ProfoundReferralsReportResponse } from './types'
export const profoundReferralsReportTool: ToolConfig<
ProfoundReferralsReportParams,
ProfoundReferralsReportResponse
> = {
id: 'profound_referrals_report',
name: 'Profound Referrals Report',
description:
'Query human referral traffic report with hourly granularity for a domain in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Domain to query referral traffic for (e.g. example.com)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601). Defaults to now',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated metrics: visits, last_visit',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated dimensions: date, hour, path, referral_source, referral_type',
},
dateInterval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date interval: hour, day, week, month, year',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"referral_source","operator":"is","value":"openai"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v2/reports/referrals',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
domain: params.domain,
start_date: params.startDate,
metrics: params.metrics.split(',').map((m) => m.trim()),
}
if (params.endDate) {
body.end_date = params.endDate
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.dateInterval) {
body.date_interval = params.dateInterval
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to query referrals report')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of rows in the report',
},
data: {
type: 'json',
description: 'Report data rows with metrics and dimension values',
properties: {
metrics: {
type: 'json',
description: 'Array of metric values matching requested metrics order',
},
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,144 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundSentimentReportParams, ProfoundSentimentReportResponse } from './types'
export const profoundSentimentReportTool: ToolConfig<
ProfoundSentimentReportParams,
ProfoundSentimentReportResponse
> = {
id: 'profound_sentiment_report',
name: 'Profound Sentiment Report',
description: 'Query sentiment report for a category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601)',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated metrics: positive, negative, occurrences',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated dimensions: theme, date, region, topic, model, asset_name, tag, prompt, sentiment_type, persona',
},
dateInterval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date interval: hour, day, week, month, year',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"asset_name","operator":"is","value":"Company"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/reports/sentiment',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
category_id: params.categoryId,
start_date: params.startDate,
end_date: params.endDate,
metrics: params.metrics.split(',').map((m) => m.trim()),
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.dateInterval) {
body.date_interval = params.dateInterval
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to query sentiment report')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of rows in the report',
},
data: {
type: 'json',
description: 'Report data rows with metrics and dimension values',
properties: {
metrics: {
type: 'json',
description: 'Array of metric values matching requested metrics order',
},
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -0,0 +1,422 @@
import type { ToolResponse } from '@/tools/types'
/** Shared report response shape (visibility, sentiment, citations, bots, referrals, query fanouts, prompt volume) */
export interface ProfoundReportResponse extends ToolResponse {
output: {
totalRows: number
data: Array<{
metrics: number[]
dimensions: string[]
}>
}
}
/** Shared report query params for category-based reports */
export interface ProfoundCategoryReportParams {
apiKey: string
categoryId: string
startDate: string
endDate: string
metrics: string
dimensions?: string
dateInterval?: string
filters?: string
limit?: number
}
/** Shared report query params for domain-based reports */
export interface ProfoundDomainReportParams {
apiKey: string
domain: string
startDate: string
endDate?: string
metrics: string
dimensions?: string
dateInterval?: string
filters?: string
limit?: number
}
// --- Organization endpoints ---
export interface ProfoundListCategoriesParams {
apiKey: string
}
export interface ProfoundListCategoriesResponse extends ToolResponse {
output: {
categories: Array<{
id: string
name: string
}>
}
}
export interface ProfoundListRegionsParams {
apiKey: string
}
export interface ProfoundListRegionsResponse extends ToolResponse {
output: {
regions: Array<{
id: string
name: string
}>
}
}
export interface ProfoundListModelsParams {
apiKey: string
}
export interface ProfoundListModelsResponse extends ToolResponse {
output: {
models: Array<{
id: string
name: string
}>
}
}
export interface ProfoundListDomainsParams {
apiKey: string
}
export interface ProfoundListDomainsResponse extends ToolResponse {
output: {
domains: Array<{
id: string
name: string
createdAt: string
}>
}
}
export interface ProfoundListAssetsParams {
apiKey: string
}
export interface ProfoundListAssetsResponse extends ToolResponse {
output: {
assets: Array<{
id: string
name: string
website: string
alternateDomains: string[] | null
isOwned: boolean
createdAt: string
logoUrl: string
categoryId: string
categoryName: string
}>
}
}
export interface ProfoundListPersonasParams {
apiKey: string
}
export interface ProfoundListPersonasResponse extends ToolResponse {
output: {
personas: Array<{
id: string
name: string
categoryId: string
categoryName: string
persona: {
behavior: { painPoints: string | null; motivations: string | null }
employment: {
industry: string[]
jobTitle: string[]
companySize: string[]
roleSeniority: string[]
}
demographics: { ageRange: string[] }
}
}>
}
}
// --- Category-specific endpoints ---
export interface ProfoundCategoryTopicsParams {
apiKey: string
categoryId: string
}
export interface ProfoundCategoryTopicsResponse extends ToolResponse {
output: {
topics: Array<{
id: string
name: string
}>
}
}
export interface ProfoundCategoryTagsParams {
apiKey: string
categoryId: string
}
export interface ProfoundCategoryTagsResponse extends ToolResponse {
output: {
tags: Array<{
id: string
name: string
}>
}
}
export interface ProfoundCategoryPromptsParams {
apiKey: string
categoryId: string
limit?: number
cursor?: string
orderDir?: string
promptType?: string
topicId?: string
tagId?: string
regionId?: string
platformId?: string
}
export interface ProfoundCategoryPromptsResponse extends ToolResponse {
output: {
totalRows: number
nextCursor: string | null
prompts: Array<{
id: string
prompt: string
promptType: string
topicId: string
topicName: string
tags: Array<{ id: string; name: string }>
regions: Array<{ id: string; name: string }>
platforms: Array<{ id: string; name: string }>
createdAt: string
}>
}
}
export interface ProfoundCategoryAssetsParams {
apiKey: string
categoryId: string
}
export interface ProfoundCategoryAssetsResponse extends ToolResponse {
output: {
assets: Array<{
id: string
name: string
website: string
alternateDomains: string[] | null
isOwned: boolean
createdAt: string
logoUrl: string
}>
}
}
export interface ProfoundCategoryPersonasParams {
apiKey: string
categoryId: string
}
export interface ProfoundCategoryPersonasResponse extends ToolResponse {
output: {
personas: Array<{
id: string
name: string
persona: {
behavior: { painPoints: string | null; motivations: string | null }
employment: {
industry: string[]
jobTitle: string[]
companySize: string[]
roleSeniority: string[]
}
demographics: { ageRange: string[] }
}
}>
}
}
// --- Reports ---
export type ProfoundVisibilityReportParams = ProfoundCategoryReportParams
export type ProfoundVisibilityReportResponse = ProfoundReportResponse
export type ProfoundSentimentReportParams = ProfoundCategoryReportParams
export type ProfoundSentimentReportResponse = ProfoundReportResponse
export type ProfoundCitationsReportParams = ProfoundCategoryReportParams
export type ProfoundCitationsReportResponse = ProfoundReportResponse
export type ProfoundQueryFanoutsParams = ProfoundCategoryReportParams
export type ProfoundQueryFanoutsResponse = ProfoundReportResponse
export type ProfoundBotsReportParams = ProfoundDomainReportParams
export type ProfoundBotsReportResponse = ProfoundReportResponse
export type ProfoundReferralsReportParams = ProfoundDomainReportParams
export type ProfoundReferralsReportResponse = ProfoundReportResponse
// --- Prompts ---
export interface ProfoundPromptAnswersParams {
apiKey: string
categoryId: string
startDate: string
endDate: string
filters?: string
limit?: number
}
export interface ProfoundPromptAnswersResponse extends ToolResponse {
output: {
totalRows: number
data: Array<{
prompt: string | null
promptType: string | null
response: string | null
mentions: string[] | null
citations: string[] | null
topic: string | null
region: string | null
model: string | null
asset: string | null
createdAt: string | null
}>
}
}
// --- Agent Analytics ---
export interface ProfoundRawLogsParams {
apiKey: string
domain: string
startDate: string
endDate?: string
dimensions?: string
filters?: string
limit?: number
}
export interface ProfoundRawLogsResponse extends ToolResponse {
output: {
totalRows: number
data: Array<{
metrics: number[]
dimensions: string[]
}>
}
}
export interface ProfoundBotLogsParams {
apiKey: string
domain: string
startDate: string
endDate?: string
dimensions?: string
filters?: string
limit?: number
}
export interface ProfoundBotLogsResponse extends ToolResponse {
output: {
totalRows: number
data: Array<{
metrics: number[]
dimensions: string[]
}>
}
}
// --- Content ---
export interface ProfoundListOptimizationsParams {
apiKey: string
assetId: string
limit?: number
offset?: number
}
export interface ProfoundListOptimizationsResponse extends ToolResponse {
output: {
totalRows: number
optimizations: Array<{
id: string
title: string
createdAt: string
extractedInput: string | null
type: string
status: string
}>
}
}
export interface ProfoundOptimizationAnalysisParams {
apiKey: string
assetId: string
contentId: string
}
export interface ProfoundOptimizationAnalysisResponse extends ToolResponse {
output: {
content: {
format: string
value: string
}
aeoContentScore: {
value: number
targetZone: { low: number; high: number }
} | null
analysis: {
breakdown: Array<{
title: string
weight: number
score: number
}>
}
recommendations: Array<{
title: string
status: string
impact: { section: string; score: number } | null
suggestion: { text: string; rationale: string }
}>
}
}
// --- Prompt Volumes ---
export interface ProfoundPromptVolumeParams {
apiKey: string
startDate: string
endDate: string
metrics: string
dimensions?: string
dateInterval?: string
filters?: string
limit?: number
}
export interface ProfoundPromptVolumeResponse extends ToolResponse {
output: {
totalRows: number
data: Array<{
metrics: number[]
dimensions: string[]
}>
}
}
export interface ProfoundCitationPromptsParams {
apiKey: string
inputDomain: string
}
export interface ProfoundCitationPromptsResponse extends ToolResponse {
output: {
data: unknown
}
}

View File

@@ -0,0 +1,145 @@
import type { ToolConfig } from '@/tools/types'
import type { ProfoundVisibilityReportParams, ProfoundVisibilityReportResponse } from './types'
export const profoundVisibilityReportTool: ToolConfig<
ProfoundVisibilityReportParams,
ProfoundVisibilityReportResponse
> = {
id: 'profound_visibility_report',
name: 'Profound Visibility Report',
description: 'Query AI visibility report for a category in Profound',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Profound API Key',
},
categoryId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category ID (UUID)',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date (YYYY-MM-DD or ISO 8601)',
},
endDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date (YYYY-MM-DD or ISO 8601)',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Comma-separated metrics: share_of_voice, mentions_count, visibility_score, executions, average_position',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated dimensions: date, region, topic, model, asset_name, prompt, tag, persona',
},
dateInterval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date interval: hour, day, week, month, year',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects, e.g. [{"field":"asset_name","operator":"is","value":"Company"}]',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (default 10000, max 50000)',
},
},
request: {
url: 'https://api.tryprofound.com/v1/reports/visibility',
method: 'POST',
headers: (params) => ({
'X-API-Key': params.apiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
category_id: params.categoryId,
start_date: params.startDate,
end_date: params.endDate,
metrics: params.metrics.split(',').map((m) => m.trim()),
}
if (params.dimensions) {
body.dimensions = params.dimensions.split(',').map((d) => d.trim())
}
if (params.dateInterval) {
body.date_interval = params.dateInterval
}
if (params.filters) {
try {
body.filters = JSON.parse(params.filters)
} catch {
throw new Error('Invalid JSON in filters parameter')
}
}
if (params.limit != null) {
body.pagination = { limit: params.limit }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail?.[0]?.msg || 'Failed to query visibility report')
}
return {
success: true,
output: {
totalRows: data.info?.total_rows ?? 0,
data: (data.data ?? []).map((row: { metrics: number[]; dimensions: string[] }) => ({
metrics: row.metrics ?? [],
dimensions: row.dimensions ?? [],
})),
},
}
},
outputs: {
totalRows: {
type: 'number',
description: 'Total number of rows in the report',
},
data: {
type: 'json',
description: 'Report data rows with metrics and dimension values',
properties: {
metrics: {
type: 'json',
description: 'Array of metric values matching requested metrics order',
},
dimensions: {
type: 'json',
description: 'Array of dimension values matching requested dimensions order',
},
},
},
},
}

View File

@@ -1777,6 +1777,32 @@ import {
posthogUpdatePropertyDefinitionTool,
posthogUpdateSurveyTool,
} from '@/tools/posthog'
import {
profoundBotLogsTool,
profoundBotsReportTool,
profoundCategoryAssetsTool,
profoundCategoryPersonasTool,
profoundCategoryPromptsTool,
profoundCategoryTagsTool,
profoundCategoryTopicsTool,
profoundCitationPromptsTool,
profoundCitationsReportTool,
profoundListAssetsTool,
profoundListCategoriesTool,
profoundListDomainsTool,
profoundListModelsTool,
profoundListOptimizationsTool,
profoundListPersonasTool,
profoundListRegionsTool,
profoundOptimizationAnalysisTool,
profoundPromptAnswersTool,
profoundPromptVolumeTool,
profoundQueryFanoutsTool,
profoundRawLogsTool,
profoundReferralsReportTool,
profoundSentimentReportTool,
profoundVisibilityReportTool,
} from '@/tools/profound'
import { pulseParserTool, pulseParserV2Tool } from '@/tools/pulse'
import { qdrantFetchTool, qdrantSearchTool, qdrantUpsertTool } from '@/tools/qdrant'
import { quiverImageToSvgTool, quiverListModelsTool, quiverTextToSvgTool } from '@/tools/quiver'
@@ -3631,6 +3657,30 @@ export const tools: Record<string, ToolConfig> = {
google_slides_insert_text: googleSlidesInsertTextTool,
perplexity_chat: perplexityChatTool,
perplexity_search: perplexitySearchTool,
profound_bot_logs: profoundBotLogsTool,
profound_bots_report: profoundBotsReportTool,
profound_category_assets: profoundCategoryAssetsTool,
profound_category_personas: profoundCategoryPersonasTool,
profound_category_prompts: profoundCategoryPromptsTool,
profound_category_tags: profoundCategoryTagsTool,
profound_category_topics: profoundCategoryTopicsTool,
profound_citation_prompts: profoundCitationPromptsTool,
profound_citations_report: profoundCitationsReportTool,
profound_list_assets: profoundListAssetsTool,
profound_list_categories: profoundListCategoriesTool,
profound_list_domains: profoundListDomainsTool,
profound_list_models: profoundListModelsTool,
profound_list_optimizations: profoundListOptimizationsTool,
profound_list_personas: profoundListPersonasTool,
profound_list_regions: profoundListRegionsTool,
profound_optimization_analysis: profoundOptimizationAnalysisTool,
profound_prompt_answers: profoundPromptAnswersTool,
profound_prompt_volume: profoundPromptVolumeTool,
profound_query_fanouts: profoundQueryFanoutsTool,
profound_raw_logs: profoundRawLogsTool,
profound_referrals_report: profoundReferralsReportTool,
profound_sentiment_report: profoundSentimentReportTool,
profound_visibility_report: profoundVisibilityReportTool,
pulse_parser: pulseParserTool,
pulse_parser_v2: pulseParserV2Tool,
quiver_image_to_svg: quiverImageToSvgTool,

View File

@@ -205,12 +205,27 @@ async function generateIconMapping(): Promise<Record<string, string>> {
* Write the icon mapping to the docs app
* This file is imported by BlockInfoCard to resolve icons automatically
*/
/**
* Sort strings to match Biome's organizeImports order:
* case-insensitive character-by-character, uppercase before lowercase as tiebreaker.
*/
function biomeSortCompare(a: string, b: string): number {
const minLen = Math.min(a.length, b.length)
for (let i = 0; i < minLen; i++) {
const al = a[i].toLowerCase()
const bl = b[i].toLowerCase()
if (al !== bl) return al < bl ? -1 : 1
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1
}
return a.length - b.length
}
function writeIconMapping(iconMapping: Record<string, string>): void {
try {
const iconMappingPath = path.join(rootDir, 'apps/docs/components/ui/icon-mapping.ts')
// Get unique icon names
const iconNames = [...new Set(Object.values(iconMapping))].sort()
// Get unique icon names, sorted to match Biome's organizeImports
const iconNames = [...new Set(Object.values(iconMapping))].sort(biomeSortCompare)
// Generate imports
const imports = iconNames.map((icon) => ` ${icon},`).join('\n')
@@ -508,7 +523,7 @@ function writeIntegrationsIconMapping(iconMapping: Record<string, string>): void
}
const iconMappingPath = path.join(LANDING_INTEGRATIONS_DATA_PATH, 'icon-mapping.ts')
const iconNames = [...new Set(Object.values(iconMapping))].sort()
const iconNames = [...new Set(Object.values(iconMapping))].sort(biomeSortCompare)
const imports = iconNames.map((icon) => ` ${icon},`).join('\n')
const mappingEntries = Object.entries(iconMapping)
.sort(([a], [b]) => a.localeCompare(b))
@@ -664,7 +679,16 @@ async function writeIntegrationsJson(iconMapping: Record<string, string>): Promi
integrations.sort((a, b) => a.name.localeCompare(b.name))
const jsonPath = path.join(LANDING_INTEGRATIONS_DATA_PATH, 'integrations.json')
fs.writeFileSync(jsonPath, JSON.stringify(integrations, null, 2))
// JSON.stringify always expands arrays across multiple lines. Biome's formatter
// collapses short arrays of primitives onto single lines. Post-process to match.
const json = JSON.stringify(integrations, null, 2).replace(
/\[\n(\s+"[^"\n]*"(?:,\n\s+"[^"\n]*")*)\n\s+\]/g,
(_match, inner) => {
const items = (inner as string).split(',\n').map((s: string) => s.trim())
return `[${items.join(', ')}]`
}
)
fs.writeFileSync(jsonPath, `${json}\n`)
console.log(`✓ Integration data written: ${integrations.length} integrations → ${jsonPath}`)
} catch (error) {
console.error('Error writing integrations JSON:', error)
@@ -2813,7 +2837,7 @@ function updateMetaJson() {
pages: items,
}
fs.writeFileSync(metaJsonPath, JSON.stringify(metaJson, null, 2))
fs.writeFileSync(metaJsonPath, `${JSON.stringify(metaJson, null, 2)}\n`)
console.log(`Updated meta.json with ${items.length} entries`)
}