mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(crms): added hubspot, asana, trello, salesforce, pipedrive tools and triggers (#1871)
* trello integration added * added asana integration * added pipedrive (need to finish testing) * finished pipedrive * finished hubspot, need to test more * make oauth required modal scrollable * edited layout and fixed merge conflicts * salesforce working, need to add more operations * added all salesforce tools * hubspot triggers working, plus other fixes * fixed payload and added hubspot triggers * fixed test * build fix * rebase * updated docs * oauth required modal * fixed icons * fixed hubspot scopes parsing * reduce scopes of salesforce oauth * cleaned up scopes * lint * aligned oauth.ts, auth.ts, and block definitions for all 29 providers * updated icons * fixed logos and updated docs * revert changes to unused file --------- Co-authored-by: Adam Gough <adamgough@Adams-MacBook-Pro.local> Co-authored-by: waleed <waleed> Co-authored-by: aadamgough <adam@sim.ai>
This commit is contained in:
170
apps/docs/content/docs/en/tools/asana.mdx
Normal file
170
apps/docs/content/docs/en/tools/asana.mdx
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: Asana
|
||||
description: Interact with Asana
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="asana"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
|
||||
|
||||
viewBox='781.361 0 944.893 873.377'
|
||||
>
|
||||
<radialGradient
|
||||
id='asana_radial_gradient'
|
||||
cx='943.992'
|
||||
cy='1221.416'
|
||||
r='.663'
|
||||
gradientTransform='matrix(944.8934 0 0 -873.3772 -890717.875 1067234.75)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop offset='0' stopColor='#ffb900' />
|
||||
<stop offset='.6' stopColor='#f95d8f' />
|
||||
<stop offset='.999' stopColor='#f95353' />
|
||||
</radialGradient>
|
||||
<path
|
||||
fill='url(#asana_radial_gradient)'
|
||||
d='M1520.766 462.371c-113.508 0-205.508 92-205.508 205.488 0 113.499 92 205.518 205.508 205.518 113.489 0 205.488-92.019 205.488-205.518 0-113.488-91.999-205.488-205.488-205.488zm-533.907.01c-113.489.01-205.498 91.99-205.498 205.488 0 113.489 92.009 205.498 205.498 205.498 113.498 0 205.508-92.009 205.508-205.498 0-113.499-92.01-205.488-205.518-205.488h.01zm472.447-256.883c0 113.489-91.999 205.518-205.488 205.518-113.508 0-205.508-92.029-205.508-205.518S1140.31 0 1253.817 0c113.489 0 205.479 92.009 205.479 205.498h.01z'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Asana into the workflow. Can read, write, and update tasks.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `asana_get_task`
|
||||
|
||||
Retrieve a single task by GID or get multiple tasks with filters
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `taskGid` | string | No | The globally unique identifier \(GID\) of the task. If not provided, will get multiple tasks. |
|
||||
| `workspace` | string | No | Workspace GID to filter tasks \(required when not using taskGid\) |
|
||||
| `project` | string | No | Project GID to filter tasks |
|
||||
| `limit` | number | No | Maximum number of tasks to return \(default: 50\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Single task details or array of tasks, depending on whether taskGid was provided |
|
||||
|
||||
### `asana_create_task`
|
||||
|
||||
Create a new task in Asana
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `workspace` | string | Yes | Workspace GID where the task will be created |
|
||||
| `name` | string | Yes | Name of the task |
|
||||
| `notes` | string | No | Notes or description for the task |
|
||||
| `assignee` | string | No | User GID to assign the task to |
|
||||
| `due_on` | string | No | Due date in YYYY-MM-DD format |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created task details with timestamp, gid, name, notes, and permalink |
|
||||
|
||||
### `asana_update_task`
|
||||
|
||||
Update an existing task in Asana
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `taskGid` | string | Yes | The globally unique identifier \(GID\) of the task to update |
|
||||
| `name` | string | No | Updated name for the task |
|
||||
| `notes` | string | No | Updated notes or description for the task |
|
||||
| `assignee` | string | No | Updated assignee user GID |
|
||||
| `completed` | boolean | No | Mark task as completed or not completed |
|
||||
| `due_on` | string | No | Updated due date in YYYY-MM-DD format |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated task details with timestamp, gid, name, notes, and modified timestamp |
|
||||
|
||||
### `asana_get_projects`
|
||||
|
||||
Retrieve all projects from an Asana workspace
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `workspace` | string | Yes | Workspace GID to retrieve projects from |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | List of projects with their gid, name, and resource type |
|
||||
|
||||
### `asana_search_tasks`
|
||||
|
||||
Search for tasks in an Asana workspace
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `workspace` | string | Yes | Workspace GID to search tasks in |
|
||||
| `text` | string | No | Text to search for in task names |
|
||||
| `assignee` | string | No | Filter tasks by assignee user GID |
|
||||
| `projects` | array | No | Array of project GIDs to filter tasks by |
|
||||
| `completed` | boolean | No | Filter by completion status |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | List of tasks matching the search criteria |
|
||||
|
||||
### `asana_add_comment`
|
||||
|
||||
Add a comment (story) to an Asana task
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `taskGid` | string | Yes | The globally unique identifier \(GID\) of the task |
|
||||
| `text` | string | Yes | The text content of the comment |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Comment details including gid, text, created timestamp, and author |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `asana`
|
||||
289
apps/docs/content/docs/en/tools/hubspot.mdx
Normal file
289
apps/docs/content/docs/en/tools/hubspot.mdx
Normal file
@@ -0,0 +1,289 @@
|
||||
---
|
||||
title: HubSpot
|
||||
description: Interact with HubSpot CRM or trigger workflows from HubSpot events
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="hubspot"
|
||||
color="#FF7A59"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
role='img'
|
||||
viewBox='0 0 24 24'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
fill='currentColor'
|
||||
>
|
||||
<path d='M18.164 7.93V5.084a2.198 2.198 0 001.267-1.978v-.067A2.2 2.2 0 0017.238.845h-.067a2.2 2.2 0 00-2.193 2.193v.067a2.196 2.196 0 001.252 1.973l.013.006v2.852a6.22 6.22 0 00-2.969 1.31l.012-.01-7.828-6.095A2.497 2.497 0 104.3 4.656l-.012.006 7.697 5.991a6.176 6.176 0 00-1.038 3.446c0 1.343.425 2.588 1.147 3.607l-.013-.02-2.342 2.343a1.968 1.968 0 00-.58-.095h-.002a2.033 2.033 0 102.033 2.033 1.978 1.978 0 00-.1-.595l.005.014 2.317-2.317a6.247 6.247 0 104.782-11.134l-.036-.005zm-.964 9.378a3.206 3.206 0 113.215-3.207v.002a3.206 3.206 0 01-3.207 3.207z' />
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[HubSpot](https://www.hubspot.com) is a comprehensive CRM platform that provides a full suite of marketing, sales, and customer service tools to help businesses grow better. With its powerful automation capabilities and extensive API, HubSpot has become one of the world's leading CRM platforms, serving businesses of all sizes across industries.
|
||||
|
||||
HubSpot CRM offers a complete solution for managing customer relationships, from initial contact through to long-term customer success. The platform combines contact management, deal tracking, marketing automation, and customer service tools into a unified system that helps teams stay aligned and focused on customer success.
|
||||
|
||||
Key features of HubSpot CRM include:
|
||||
|
||||
- Contact & Company Management: Comprehensive database for storing and organizing customer and prospect information
|
||||
- Deal Pipeline: Visual sales pipeline for tracking opportunities through customizable stages
|
||||
- Marketing Events: Track and manage marketing campaigns and events with detailed attribution
|
||||
- Ticket Management: Customer support ticketing system for tracking and resolving customer issues
|
||||
- Quotes & Line Items: Create and manage sales quotes with detailed product line items
|
||||
- User & Team Management: Organize teams, assign ownership, and track user activity across the platform
|
||||
|
||||
In Sim, the HubSpot integration enables your AI agents to seamlessly interact with your CRM data and automate key business processes. This creates powerful opportunities for intelligent lead qualification, automated contact enrichment, deal management, customer support automation, and data synchronization across your tech stack. The integration allows agents to create, retrieve, update, and search across all major HubSpot objects, enabling sophisticated workflows that can respond to CRM events, maintain data quality, and ensure your team has the most up-to-date customer information. By connecting Sim with HubSpot, you can build AI agents that automatically qualify leads, route support tickets, update deal stages based on customer interactions, generate quotes, and keep your CRM data synchronized with other business systems—ultimately increasing team productivity and improving customer experiences.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate HubSpot into your workflow. Manage contacts, companies, deals, tickets, and other CRM objects with powerful automation capabilities. Can be used in trigger mode to start workflows when contacts are created, deleted, or updated.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `hubspot_get_users`
|
||||
|
||||
Retrieve all users from HubSpot account
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Number of results to return \(default: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Users data |
|
||||
|
||||
### `hubspot_list_contacts`
|
||||
|
||||
Retrieve all contacts from HubSpot account with pagination support
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Maximum number of results per page \(max 100, default 100\) |
|
||||
| `after` | string | No | Pagination cursor for next page of results |
|
||||
| `properties` | string | No | Comma-separated list of properties to return \(e.g., "email,firstname,lastname"\) |
|
||||
| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Contacts data |
|
||||
|
||||
### `hubspot_get_contact`
|
||||
|
||||
Retrieve a single contact by ID or email from HubSpot
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `contactId` | string | Yes | The ID or email of the contact to retrieve |
|
||||
| `idProperty` | string | No | Property to use as unique identifier \(e.g., "email"\). If not specified, uses record ID |
|
||||
| `properties` | string | No | Comma-separated list of properties to return |
|
||||
| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Contact data |
|
||||
|
||||
### `hubspot_create_contact`
|
||||
|
||||
Create a new contact in HubSpot. Requires at least one of: email, firstname, or lastname
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `properties` | object | Yes | Contact properties as JSON object. Must include at least one of: email, firstname, or lastname |
|
||||
| `associations` | array | No | Array of associations to create with the contact \(e.g., companies, deals\). Each object should have "to" \(with "id"\) and "types" \(with "associationCategory" and "associationTypeId"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created contact data |
|
||||
|
||||
### `hubspot_update_contact`
|
||||
|
||||
Update an existing contact in HubSpot by ID or email
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `contactId` | string | Yes | The ID or email of the contact to update |
|
||||
| `idProperty` | string | No | Property to use as unique identifier \(e.g., "email"\). If not specified, uses record ID |
|
||||
| `properties` | object | Yes | Contact properties to update as JSON object |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated contact data |
|
||||
|
||||
### `hubspot_search_contacts`
|
||||
|
||||
Search for contacts in HubSpot using filters, sorting, and queries
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `filterGroups` | array | No | Array of filter groups. Each group contains filters with propertyName, operator, and value |
|
||||
| `sorts` | array | No | Array of sort objects with propertyName and direction \("ASCENDING" or "DESCENDING"\) |
|
||||
| `query` | string | No | Search query string |
|
||||
| `properties` | array | No | Array of property names to return |
|
||||
| `limit` | number | No | Maximum number of results to return \(max 100\) |
|
||||
| `after` | string | No | Pagination cursor for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Search results |
|
||||
|
||||
### `hubspot_list_companies`
|
||||
|
||||
Retrieve all companies from HubSpot account with pagination support
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Maximum number of results per page \(max 100, default 100\) |
|
||||
| `after` | string | No | Pagination cursor for next page of results |
|
||||
| `properties` | string | No | Comma-separated list of properties to return |
|
||||
| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Companies data |
|
||||
|
||||
### `hubspot_get_company`
|
||||
|
||||
Retrieve a single company by ID or domain from HubSpot
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `companyId` | string | Yes | The ID or domain of the company to retrieve |
|
||||
| `idProperty` | string | No | Property to use as unique identifier \(e.g., "domain"\). If not specified, uses record ID |
|
||||
| `properties` | string | No | Comma-separated list of properties to return |
|
||||
| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Company data |
|
||||
|
||||
### `hubspot_create_company`
|
||||
|
||||
Create a new company in HubSpot
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `properties` | object | Yes | Company properties as JSON object \(e.g., name, domain, city, industry\) |
|
||||
| `associations` | array | No | Array of associations to create with the company |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created company data |
|
||||
|
||||
### `hubspot_update_company`
|
||||
|
||||
Update an existing company in HubSpot by ID or domain
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `companyId` | string | Yes | The ID or domain of the company to update |
|
||||
| `idProperty` | string | No | Property to use as unique identifier \(e.g., "domain"\). If not specified, uses record ID |
|
||||
| `properties` | object | Yes | Company properties to update as JSON object |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated company data |
|
||||
|
||||
### `hubspot_search_companies`
|
||||
|
||||
Search for companies in HubSpot using filters, sorting, and queries
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `filterGroups` | array | No | Array of filter groups. Each group contains filters with propertyName, operator, and value |
|
||||
| `sorts` | array | No | Array of sort objects with propertyName and direction \("ASCENDING" or "DESCENDING"\) |
|
||||
| `query` | string | No | Search query string |
|
||||
| `properties` | array | No | Array of property names to return |
|
||||
| `limit` | number | No | Maximum number of results to return \(max 100\) |
|
||||
| `after` | string | No | Pagination cursor for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Search results |
|
||||
|
||||
### `hubspot_list_deals`
|
||||
|
||||
Retrieve all deals from HubSpot account with pagination support
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Maximum number of results per page \(max 100, default 100\) |
|
||||
| `after` | string | No | Pagination cursor for next page of results |
|
||||
| `properties` | string | No | Comma-separated list of properties to return |
|
||||
| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Deals data |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `hubspot`
|
||||
@@ -3,6 +3,7 @@
|
||||
"index",
|
||||
"airtable",
|
||||
"arxiv",
|
||||
"asana",
|
||||
"browser_use",
|
||||
"clay",
|
||||
"confluence",
|
||||
@@ -21,6 +22,7 @@
|
||||
"google_search",
|
||||
"google_sheets",
|
||||
"google_vault",
|
||||
"hubspot",
|
||||
"huggingface",
|
||||
"hunter",
|
||||
"image_generator",
|
||||
@@ -45,11 +47,13 @@
|
||||
"parallel_ai",
|
||||
"perplexity",
|
||||
"pinecone",
|
||||
"pipedrive",
|
||||
"postgresql",
|
||||
"qdrant",
|
||||
"reddit",
|
||||
"resend",
|
||||
"s3",
|
||||
"salesforce",
|
||||
"schedule",
|
||||
"serper",
|
||||
"sharepoint",
|
||||
@@ -63,6 +67,7 @@
|
||||
"telegram",
|
||||
"thinking",
|
||||
"translate",
|
||||
"trello",
|
||||
"twilio_sms",
|
||||
"twilio_voice",
|
||||
"typeform",
|
||||
|
||||
@@ -9,10 +9,42 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
type="microsoft_planner"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon" fill='currentColor' viewBox='-1 -1 27 27' xmlns='http://www.w3.org/2000/svg'>
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<g clipPath='url(#msplanner_clip0)'>
|
||||
<path
|
||||
d='M8.25809 15.7412C7.22488 16.7744 5.54971 16.7744 4.5165 15.7412L0.774909 11.9996C-0.258303 10.9664 -0.258303 9.29129 0.774908 8.25809L4.5165 4.51655C5.54971 3.48335 7.22488 3.48335 8.25809 4.51655L11.9997 8.2581C13.0329 9.29129 13.0329 10.9664 11.9997 11.9996L8.25809 15.7412Z'
|
||||
fill='url(#msplanner_paint0_linear)'
|
||||
/>
|
||||
<path
|
||||
d='M8.25809 15.7412C7.22488 16.7744 5.54971 16.7744 4.5165 15.7412L0.774909 11.9996C-0.258303 10.9664 -0.258303 9.29129 0.774908 8.25809L4.5165 4.51655C5.54971 3.48335 7.22488 3.48335 8.25809 4.51655L11.9997 8.2581C13.0329 9.29129 13.0329 10.9664 11.9997 11.9996L8.25809 15.7412Z'
|
||||
fill='url(#msplanner_paint1_linear)'
|
||||
/>
|
||||
<path
|
||||
d='M0.774857 11.9999C1.80809 13.0331 3.48331 13.0331 4.51655 11.9999L15.7417 0.774926C16.7749 -0.258304 18.4501 -0.258309 19.4834 0.774914L23.225 4.51655C24.2583 5.54977 24.2583 7.22496 23.225 8.25819L11.9999 19.4832C10.9667 20.5164 9.29146 20.5164 8.25822 19.4832L0.774857 11.9999Z'
|
||||
fill='url(#msplanner_paint2_linear)'
|
||||
/>
|
||||
<path
|
||||
d='M0.774857 11.9999C1.80809 13.0331 3.48331 13.0331 4.51655 11.9999L15.7417 0.774926C16.7749 -0.258304 18.4501 -0.258309 19.4834 0.774914L23.225 4.51655C24.2583 5.54977 24.2583 7.22496 23.225 8.25819L11.9999 19.4832C10.9667 20.5164 9.29146 20.5164 8.25822 19.4832L0.774857 11.9999Z'
|
||||
fill='url(#msplanner_paint3_linear)'
|
||||
/>
|
||||
<path
|
||||
d='M4.51642 15.7413C5.54966 16.7746 7.22487 16.7746 8.25812 15.7413L15.7415 8.25803C16.7748 7.2248 18.45 7.2248 19.4832 8.25803L23.2249 11.9997C24.2582 13.0329 24.2582 14.7081 23.2249 15.7413L15.7415 23.2246C14.7083 24.2579 13.033 24.2579 11.9998 23.2246L4.51642 15.7413Z'
|
||||
fill='url(#msplanner_paint4_linear)'
|
||||
/>
|
||||
<path
|
||||
d='M4.51642 15.7413C5.54966 16.7746 7.22487 16.7746 8.25812 15.7413L15.7415 8.25803C16.7748 7.2248 18.45 7.2248 19.4832 8.25803L23.2249 11.9997C24.2582 13.0329 24.2582 14.7081 23.2249 15.7413L15.7415 23.2246C14.7083 24.2579 13.033 24.2579 11.9998 23.2246L4.51642 15.7413Z'
|
||||
fill='url(#msplanner_paint5_linear)'
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='paint0_linear_3984_11038'
|
||||
id='msplanner_paint0_linear'
|
||||
x1='6.38724'
|
||||
y1='3.74167'
|
||||
x2='2.15779'
|
||||
@@ -23,7 +55,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop offset='1' stopColor='#541278' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint1_linear_3984_11038'
|
||||
id='msplanner_paint1_linear'
|
||||
x1='8.38032'
|
||||
y1='11.0696'
|
||||
x2='4.94062'
|
||||
@@ -34,7 +66,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop offset='1' stopColor='#7034B0' stopOpacity='0' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint2_linear_3984_11038'
|
||||
id='msplanner_paint2_linear'
|
||||
x1='18.3701'
|
||||
y1='-3.33385e-05'
|
||||
x2='9.85717'
|
||||
@@ -45,7 +77,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop offset='1' stopColor='#6C0F71' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint3_linear_3984_11038'
|
||||
id='msplanner_paint3_linear'
|
||||
x1='18.3701'
|
||||
y1='-3.33385e-05'
|
||||
x2='9.85717'
|
||||
@@ -57,7 +89,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop offset='1' stopColor='#8F28B3' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint4_linear_3984_11038'
|
||||
id='msplanner_paint4_linear'
|
||||
x1='18.0002'
|
||||
y1='7.49958'
|
||||
x2='14.0004'
|
||||
@@ -68,7 +100,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop offset='1' stopColor='#00479E' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint5_linear_3984_11038'
|
||||
id='msplanner_paint5_linear'
|
||||
x1='18.2164'
|
||||
y1='7.92626'
|
||||
x2='10.5237'
|
||||
@@ -78,31 +110,10 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop stopColor='#3DCBFF' />
|
||||
<stop offset='1' stopColor='#4A40D4' />
|
||||
</linearGradient>
|
||||
<clipPath id='msplanner_clip0'>
|
||||
<rect fill='white' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<path
|
||||
d='M8.25809 15.7412C7.22488 16.7744 5.54971 16.7744 4.5165 15.7412L0.774909 11.9996C-0.258303 10.9664 -0.258303 9.29129 0.774908 8.25809L4.5165 4.51655C5.54971 3.48335 7.22488 3.48335 8.25809 4.51655L11.9997 8.2581C13.0329 9.29129 13.0329 10.9664 11.9997 11.9996L8.25809 15.7412Z'
|
||||
fill='url(#paint0_linear_3984_11038)'
|
||||
/>
|
||||
<path
|
||||
d='M8.25809 15.7412C7.22488 16.7744 5.54971 16.7744 4.5165 15.7412L0.774909 11.9996C-0.258303 10.9664 -0.258303 9.29129 0.774908 8.25809L4.5165 4.51655C5.54971 3.48335 7.22488 3.48335 8.25809 4.51655L11.9997 8.2581C13.0329 9.29129 13.0329 10.9664 11.9997 11.9996L8.25809 15.7412Z'
|
||||
fill='url(#paint1_linear_3984_11038)'
|
||||
/>
|
||||
<path
|
||||
d='M0.774857 11.9999C1.80809 13.0331 3.48331 13.0331 4.51655 11.9999L15.7417 0.774926C16.7749 -0.258304 18.4501 -0.258309 19.4834 0.774914L23.225 4.51655C24.2583 5.54977 24.2583 7.22496 23.225 8.25819L11.9999 19.4832C10.9667 20.5164 9.29146 20.5164 8.25822 19.4832L0.774857 11.9999Z'
|
||||
fill='url(#paint2_linear_3984_11038)'
|
||||
/>
|
||||
<path
|
||||
d='M0.774857 11.9999C1.80809 13.0331 3.48331 13.0331 4.51655 11.9999L15.7417 0.774926C16.7749 -0.258304 18.4501 -0.258309 19.4834 0.774914L23.225 4.51655C24.2583 5.54977 24.2583 7.22496 23.225 8.25819L11.9999 19.4832C10.9667 20.5164 9.29146 20.5164 8.25822 19.4832L0.774857 11.9999Z'
|
||||
fill='url(#paint3_linear_3984_11038)'
|
||||
/>
|
||||
<path
|
||||
d='M4.51642 15.7413C5.54966 16.7746 7.22487 16.7746 8.25812 15.7413L15.7415 8.25803C16.7748 7.2248 18.45 7.2248 19.4832 8.25803L23.2249 11.9997C24.2582 13.0329 24.2582 14.7081 23.2249 15.7413L15.7415 23.2246C14.7083 24.2579 13.033 24.2579 11.9998 23.2246L4.51642 15.7413Z'
|
||||
fill='url(#paint4_linear_3984_11038)'
|
||||
/>
|
||||
<path
|
||||
d='M4.51642 15.7413C5.54966 16.7746 7.22487 16.7746 8.25812 15.7413L15.7415 8.25803C16.7748 7.2248 18.45 7.2248 19.4832 8.25803L23.2249 11.9997C24.2582 13.0329 24.2582 14.7081 23.2249 15.7413L15.7415 23.2246C14.7083 24.2579 13.033 24.2579 11.9998 23.2246L4.51642 15.7413Z'
|
||||
fill='url(#paint5_linear_3984_11038)'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
d='M1140.333,561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003 h138.395C1097.783,466.699,1140.134,509.051,1140.333,561.355z'
|
||||
/>
|
||||
<linearGradient
|
||||
id='a'
|
||||
id='msteams_gradient_a'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
x1='198.099'
|
||||
y1='1683.0726'
|
||||
@@ -69,7 +69,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop offset='1' stopColor='#3940ab' />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill='url(#a)'
|
||||
fill='url(#msteams_gradient_a)'
|
||||
d='M95.01,466.5h950.312c52.473,0,95.01,42.538,95.01,95.01v950.312c0,52.473-42.538,95.01-95.01,95.01 H95.01c-52.473,0-95.01-42.538-95.01-95.01V561.51C0,509.038,42.538,466.5,95.01,466.5z'
|
||||
/>
|
||||
<path
|
||||
|
||||
452
apps/docs/content/docs/en/tools/pipedrive.mdx
Normal file
452
apps/docs/content/docs/en/tools/pipedrive.mdx
Normal file
@@ -0,0 +1,452 @@
|
||||
---
|
||||
title: Pipedrive
|
||||
description: Interact with Pipedrive CRM
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="pipedrive"
|
||||
color="#2E6936"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 304 304'
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d='M59.6807,81.1772 C59.6807,101.5343 70.0078,123.4949 92.7336,123.4949 C109.5872,123.4949 126.6277,110.3374 126.6277,80.8785 C126.6277,55.0508 113.232,37.7119 93.2944,37.7119 C77.0483,37.7119 59.6807,49.1244 59.6807,81.1772 Z M101.3006,0 C142.0482,0 169.4469,32.2728 169.4469,80.3126 C169.4469,127.5978 140.584,160.60942 99.3224,160.60942 C79.6495,160.60942 67.0483,152.1836 60.4595,146.0843 C60.5063,147.5305 60.5374,149.1497 60.5374,150.8788 L60.5374,215 L18.32565,215 L18.32565,44.157 C18.32565,41.6732 17.53126,40.8873 15.07021,40.8873 L0.5531,40.8873 L0.5531,3.4741 L35.9736,3.4741 C52.282,3.4741 56.4564,11.7741 57.2508,18.1721 C63.8708,10.7524 77.5935,0 101.3006,0 Z'
|
||||
id='path-1'
|
||||
/>
|
||||
</defs>
|
||||
<g
|
||||
id='Pipedrive_letter_logo_dark'
|
||||
stroke='none'
|
||||
strokeWidth='1'
|
||||
fill='none'
|
||||
fillRule='evenodd'
|
||||
>
|
||||
<g transform='translate(67.000000, 44.000000)'>
|
||||
<mask id='mask-2' fill='white'>
|
||||
<use href='#path-1' />
|
||||
</mask>
|
||||
<use id='Clip-5' fill='#FFFFFF' xlinkHref='#path-1' />
|
||||
</g>
|
||||
</g>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Pipedrive](https://www.pipedrive.com) is a powerful sales-focused CRM platform designed to help sales teams manage leads, track deals, and optimize their sales pipeline. Built with simplicity and effectiveness in mind, Pipedrive has become a favorite among sales professionals and growing businesses worldwide for its intuitive visual pipeline management and actionable sales insights.
|
||||
|
||||
Pipedrive provides a comprehensive suite of tools for managing the entire sales process from lead capture to deal closure. With its robust API and extensive integration capabilities, Pipedrive enables sales teams to automate repetitive tasks, maintain data consistency, and focus on what matters most—closing deals.
|
||||
|
||||
Key features of Pipedrive include:
|
||||
|
||||
- Visual Sales Pipeline: Intuitive drag-and-drop interface for managing deals through customizable sales stages
|
||||
- Lead Management: Comprehensive lead inbox for capturing, qualifying, and converting potential opportunities
|
||||
- Activity Tracking: Sophisticated system for scheduling and tracking calls, meetings, emails, and tasks
|
||||
- Project Management: Built-in project tracking capabilities for post-sale customer success and delivery
|
||||
- Email Integration: Native mailbox integration for seamless communication tracking within the CRM
|
||||
|
||||
In Sim, the Pipedrive integration allows your AI agents to seamlessly interact with your sales workflow. This creates opportunities for automated lead qualification, deal creation and updates, activity scheduling, and pipeline management as part of your AI-powered sales processes. The integration enables agents to create, retrieve, update, and manage deals, leads, activities, and projects programmatically, facilitating intelligent sales automation and ensuring that critical customer information is properly tracked and acted upon. By connecting Sim with Pipedrive, you can build AI agents that maintain sales pipeline visibility, automate routine CRM tasks, qualify leads intelligently, and ensure no opportunities slip through the cracks—enhancing sales team productivity and driving consistent revenue growth.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Pipedrive into your workflow. Manage deals, contacts, sales pipeline, projects, activities, files, and communications with powerful CRM capabilities.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `pipedrive_get_all_deals`
|
||||
|
||||
Retrieve all deals from Pipedrive with optional filters
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `status` | string | No | Only fetch deals with a specific status. Values: open, won, lost. If omitted, all not deleted deals are returned |
|
||||
| `person_id` | string | No | If supplied, only deals linked to the specified person are returned |
|
||||
| `org_id` | string | No | If supplied, only deals linked to the specified organization are returned |
|
||||
| `pipeline_id` | string | No | If supplied, only deals in the specified pipeline are returned |
|
||||
| `updated_since` | string | No | If set, only deals updated after this time are returned. Format: 2025-01-01T10:20:00Z |
|
||||
| `limit` | string | No | Number of results to return \(default: 100, max: 500\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Deals data and metadata |
|
||||
|
||||
### `pipedrive_get_deal`
|
||||
|
||||
Retrieve detailed information about a specific deal
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `deal_id` | string | Yes | The ID of the deal to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Deal details |
|
||||
|
||||
### `pipedrive_create_deal`
|
||||
|
||||
Create a new deal in Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Yes | The title of the deal |
|
||||
| `value` | string | No | The monetary value of the deal |
|
||||
| `currency` | string | No | Currency code \(e.g., USD, EUR\) |
|
||||
| `person_id` | string | No | ID of the person this deal is associated with |
|
||||
| `org_id` | string | No | ID of the organization this deal is associated with |
|
||||
| `pipeline_id` | string | No | ID of the pipeline this deal should be placed in |
|
||||
| `stage_id` | string | No | ID of the stage this deal should be placed in |
|
||||
| `status` | string | No | Status of the deal: open, won, lost |
|
||||
| `expected_close_date` | string | No | Expected close date in YYYY-MM-DD format |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created deal details |
|
||||
|
||||
### `pipedrive_update_deal`
|
||||
|
||||
Update an existing deal in Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `deal_id` | string | Yes | The ID of the deal to update |
|
||||
| `title` | string | No | New title for the deal |
|
||||
| `value` | string | No | New monetary value for the deal |
|
||||
| `status` | string | No | New status: open, won, lost |
|
||||
| `stage_id` | string | No | New stage ID for the deal |
|
||||
| `expected_close_date` | string | No | New expected close date in YYYY-MM-DD format |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated deal details |
|
||||
|
||||
### `pipedrive_get_files`
|
||||
|
||||
Retrieve files from Pipedrive with optional filters
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `deal_id` | string | No | Filter files by deal ID |
|
||||
| `person_id` | string | No | Filter files by person ID |
|
||||
| `org_id` | string | No | Filter files by organization ID |
|
||||
| `limit` | string | No | Number of results to return \(default: 100, max: 500\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Files data |
|
||||
|
||||
### `pipedrive_get_mail_messages`
|
||||
|
||||
Retrieve mail threads from Pipedrive mailbox
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `folder` | string | No | Filter by folder: inbox, drafts, sent, archive \(default: inbox\) |
|
||||
| `limit` | string | No | Number of results to return \(default: 50\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Mail threads data |
|
||||
|
||||
### `pipedrive_get_mail_thread`
|
||||
|
||||
Retrieve all messages from a specific mail thread
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `thread_id` | string | Yes | The ID of the mail thread |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Mail thread messages data |
|
||||
|
||||
### `pipedrive_get_pipelines`
|
||||
|
||||
Retrieve all pipelines from Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `sort_by` | string | No | Field to sort by: id, update_time, add_time \(default: id\) |
|
||||
| `sort_direction` | string | No | Sorting direction: asc, desc \(default: asc\) |
|
||||
| `limit` | string | No | Number of results to return \(default: 100, max: 500\) |
|
||||
| `cursor` | string | No | For pagination, the marker representing the first item on the next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Pipelines data |
|
||||
|
||||
### `pipedrive_get_pipeline_deals`
|
||||
|
||||
Retrieve all deals in a specific pipeline
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `pipeline_id` | string | Yes | The ID of the pipeline |
|
||||
| `stage_id` | string | No | Filter by specific stage within the pipeline |
|
||||
| `status` | string | No | Filter by deal status: open, won, lost |
|
||||
| `limit` | string | No | Number of results to return \(default: 100, max: 500\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Pipeline deals data |
|
||||
|
||||
### `pipedrive_get_projects`
|
||||
|
||||
Retrieve all projects or a specific project from Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `project_id` | string | No | Optional: ID of a specific project to retrieve |
|
||||
| `status` | string | No | Filter by project status: open, completed, deleted \(only for listing all\) |
|
||||
| `limit` | string | No | Number of results to return \(default: 100, max: 500, only for listing all\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Projects data or single project details |
|
||||
|
||||
### `pipedrive_create_project`
|
||||
|
||||
Create a new project in Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Yes | The title of the project |
|
||||
| `description` | string | No | Description of the project |
|
||||
| `start_date` | string | No | Project start date in YYYY-MM-DD format |
|
||||
| `end_date` | string | No | Project end date in YYYY-MM-DD format |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created project details |
|
||||
|
||||
### `pipedrive_get_activities`
|
||||
|
||||
Retrieve activities (tasks) from Pipedrive with optional filters
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `deal_id` | string | No | Filter activities by deal ID |
|
||||
| `person_id` | string | No | Filter activities by person ID |
|
||||
| `org_id` | string | No | Filter activities by organization ID |
|
||||
| `type` | string | No | Filter by activity type \(call, meeting, task, deadline, email, lunch\) |
|
||||
| `done` | string | No | Filter by completion status: 0 for not done, 1 for done |
|
||||
| `limit` | string | No | Number of results to return \(default: 100, max: 500\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Activities data |
|
||||
|
||||
### `pipedrive_create_activity`
|
||||
|
||||
Create a new activity (task) in Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `subject` | string | Yes | The subject/title of the activity |
|
||||
| `type` | string | Yes | Activity type: call, meeting, task, deadline, email, lunch |
|
||||
| `due_date` | string | Yes | Due date in YYYY-MM-DD format |
|
||||
| `due_time` | string | No | Due time in HH:MM format |
|
||||
| `duration` | string | No | Duration in HH:MM format |
|
||||
| `deal_id` | string | No | ID of the deal to associate with |
|
||||
| `person_id` | string | No | ID of the person to associate with |
|
||||
| `org_id` | string | No | ID of the organization to associate with |
|
||||
| `note` | string | No | Notes for the activity |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created activity details |
|
||||
|
||||
### `pipedrive_update_activity`
|
||||
|
||||
Update an existing activity (task) in Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `activity_id` | string | Yes | The ID of the activity to update |
|
||||
| `subject` | string | No | New subject/title for the activity |
|
||||
| `due_date` | string | No | New due date in YYYY-MM-DD format |
|
||||
| `due_time` | string | No | New due time in HH:MM format |
|
||||
| `duration` | string | No | New duration in HH:MM format |
|
||||
| `done` | string | No | Mark as done: 0 for not done, 1 for done |
|
||||
| `note` | string | No | New notes for the activity |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated activity details |
|
||||
|
||||
### `pipedrive_get_leads`
|
||||
|
||||
Retrieve all leads or a specific lead from Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `lead_id` | string | No | Optional: ID of a specific lead to retrieve |
|
||||
| `archived` | string | No | Get archived leads instead of active ones |
|
||||
| `owner_id` | string | No | Filter by owner user ID |
|
||||
| `person_id` | string | No | Filter by person ID |
|
||||
| `organization_id` | string | No | Filter by organization ID |
|
||||
| `limit` | string | No | Number of results to return \(default: 100, max: 500\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Leads data or single lead details |
|
||||
|
||||
### `pipedrive_create_lead`
|
||||
|
||||
Create a new lead in Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Yes | The name of the lead |
|
||||
| `person_id` | string | No | ID of the person \(REQUIRED unless organization_id is provided\) |
|
||||
| `organization_id` | string | No | ID of the organization \(REQUIRED unless person_id is provided\) |
|
||||
| `owner_id` | string | No | ID of the user who will own the lead |
|
||||
| `value_amount` | string | No | Potential value amount |
|
||||
| `value_currency` | string | No | Currency code \(e.g., USD, EUR\) |
|
||||
| `expected_close_date` | string | No | Expected close date in YYYY-MM-DD format |
|
||||
| `visible_to` | string | No | Visibility: 1 \(Owner & followers\), 3 \(Entire company\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created lead details |
|
||||
|
||||
### `pipedrive_update_lead`
|
||||
|
||||
Update an existing lead in Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `lead_id` | string | Yes | The ID of the lead to update |
|
||||
| `title` | string | No | New name for the lead |
|
||||
| `person_id` | string | No | New person ID |
|
||||
| `organization_id` | string | No | New organization ID |
|
||||
| `owner_id` | string | No | New owner user ID |
|
||||
| `value_amount` | string | No | New value amount |
|
||||
| `value_currency` | string | No | New currency code \(e.g., USD, EUR\) |
|
||||
| `expected_close_date` | string | No | New expected close date in YYYY-MM-DD format |
|
||||
| `is_archived` | string | No | Archive the lead: true or false |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated lead details |
|
||||
|
||||
### `pipedrive_delete_lead`
|
||||
|
||||
Delete a specific lead from Pipedrive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `lead_id` | string | Yes | The ID of the lead to delete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Deletion result |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `pipedrive`
|
||||
@@ -10,7 +10,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
color="#1A223F"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon" fill='none' viewBox='0 0 49 56' xmlns='http://www.w3.org/2000/svg'>
|
||||
<g clipPath='url(#b)'>
|
||||
<g clipPath='url(#qdrant_clippath_b)'>
|
||||
<path
|
||||
d='m38.489 51.477-1.1167-30.787-2.0223-8.1167 13.498 1.429v37.242l-8.2456 4.7589-2.1138-4.5259z'
|
||||
clipRule='evenodd'
|
||||
@@ -59,11 +59,14 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
fill='#DC244C'
|
||||
fillRule='evenodd'
|
||||
/>
|
||||
<path d='m24.603 46.483v-9.5222l-7.7166-4.4411v9.5064l7.7166 4.4569z' fill='url(#a)' />
|
||||
<path
|
||||
d='m24.603 46.483v-9.5222l-7.7166-4.4411v9.5064l7.7166 4.4569z'
|
||||
fill='url(#qdrant_gradient_a)'
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='a'
|
||||
id='qdrant_gradient_a'
|
||||
x1='23.18'
|
||||
x2='15.491'
|
||||
y1='38.781'
|
||||
@@ -73,7 +76,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop stopColor='#FF3364' offset='0' />
|
||||
<stop stopColor='#C91540' stopOpacity='0' offset='1' />
|
||||
</linearGradient>
|
||||
<clipPath id='b'>
|
||||
<clipPath id='qdrant_clippath_b'>
|
||||
<rect transform='translate(.34961)' fill='#fff' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
424
apps/docs/content/docs/en/tools/salesforce.mdx
Normal file
424
apps/docs/content/docs/en/tools/salesforce.mdx
Normal file
File diff suppressed because one or more lines are too long
@@ -9,14 +9,21 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
type="supabase"
|
||||
color="#1C1C1C"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon" viewBox='0 0 27 27' xmlns='http://www.w3.org/2000/svg'>
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
fill='currentColor'
|
||||
|
||||
|
||||
viewBox='0 0 27 27'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
d='M15.4057 26.2606C14.7241 27.1195 13.3394 26.649 13.3242 25.5519L13.083 9.50684H23.8724C25.8262 9.50684 26.9157 11.7636 25.7006 13.2933L15.4057 26.2606Z'
|
||||
fill='url(#paint0_linear)'
|
||||
fill='url(#supabase_paint0_linear)'
|
||||
/>
|
||||
<path
|
||||
d='M15.4057 26.2606C14.7241 27.1195 13.3394 26.649 13.3242 25.5519L13.083 9.50684H23.8724C25.8262 9.50684 26.9157 11.7636 25.7006 13.2933L15.4057 26.2606Z'
|
||||
fill='url(#paint1_linear)'
|
||||
fill='url(#supabase_paint1_linear)'
|
||||
fillOpacity='0.2'
|
||||
/>
|
||||
<path
|
||||
@@ -25,7 +32,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='paint0_linear'
|
||||
id='supabase_paint0_linear'
|
||||
x1='13.084'
|
||||
y1='13.0655'
|
||||
x2='22.6727'
|
||||
@@ -36,7 +43,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
<stop offset='1' stopColor='#3ECF8E' />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id='paint1_linear'
|
||||
id='supabase_paint1_linear'
|
||||
x1='8.83277'
|
||||
y1='7.24485'
|
||||
x2='13.2057'
|
||||
|
||||
167
apps/docs/content/docs/en/tools/trello.mdx
Normal file
167
apps/docs/content/docs/en/tools/trello.mdx
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
title: Trello
|
||||
description: Manage Trello boards and cards
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="trello"
|
||||
color="#0052CC"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
|
||||
|
||||
viewBox='0 0 256 256'
|
||||
preserveAspectRatio='xMidYMid'
|
||||
>
|
||||
<rect fill='#0052CC' x='0' y='0' rx='32' />
|
||||
<rect fill='#FFF' x='144.64' y='33.28' rx='12' />
|
||||
<rect fill='#FFF' x='33.28' y='33.28' rx='12' />
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate with Trello to manage boards and cards. List boards, list cards, create cards, update cards, get actions, and add comments.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `trello_list_lists`
|
||||
|
||||
List all lists on a Trello board
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `boardId` | string | Yes | ID of the board to list lists from |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the operation was successful |
|
||||
| `lists` | array | Array of list objects with id, name, closed, pos, and idBoard |
|
||||
| `count` | number | Number of lists returned |
|
||||
| `error` | string | Error message if operation failed |
|
||||
|
||||
### `trello_list_cards`
|
||||
|
||||
List all cards on a Trello board
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `boardId` | string | Yes | ID of the board to list cards from |
|
||||
| `listId` | string | No | Optional: Filter cards by list ID |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the operation was successful |
|
||||
| `cards` | array | Array of card objects with id, name, desc, url, board/list IDs, labels, and due date |
|
||||
| `count` | number | Number of cards returned |
|
||||
| `error` | string | Error message if operation failed |
|
||||
|
||||
### `trello_create_card`
|
||||
|
||||
Create a new card on a Trello board
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `boardId` | string | Yes | ID of the board to create the card on |
|
||||
| `listId` | string | Yes | ID of the list to create the card in |
|
||||
| `name` | string | Yes | Name/title of the card |
|
||||
| `desc` | string | No | Description of the card |
|
||||
| `pos` | string | No | Position of the card \(top, bottom, or positive float\) |
|
||||
| `due` | string | No | Due date \(ISO 8601 format\) |
|
||||
| `labels` | string | No | Comma-separated list of label IDs |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the card was created successfully |
|
||||
| `card` | object | The created card object with id, name, desc, url, and other properties |
|
||||
| `error` | string | Error message if operation failed |
|
||||
|
||||
### `trello_update_card`
|
||||
|
||||
Update an existing card on Trello
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `cardId` | string | Yes | ID of the card to update |
|
||||
| `name` | string | No | New name/title of the card |
|
||||
| `desc` | string | No | New description of the card |
|
||||
| `closed` | boolean | No | Archive/close the card \(true\) or reopen it \(false\) |
|
||||
| `idList` | string | No | Move card to a different list |
|
||||
| `due` | string | No | Due date \(ISO 8601 format\) |
|
||||
| `dueComplete` | boolean | No | Mark the due date as complete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the card was updated successfully |
|
||||
| `card` | object | The updated card object with id, name, desc, url, and other properties |
|
||||
| `error` | string | Error message if operation failed |
|
||||
|
||||
### `trello_get_actions`
|
||||
|
||||
Get activity/actions from a board or card
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `boardId` | string | No | ID of the board to get actions from \(either boardId or cardId required\) |
|
||||
| `cardId` | string | No | ID of the card to get actions from \(either boardId or cardId required\) |
|
||||
| `filter` | string | No | Filter actions by type \(e.g., "commentCard,updateCard,createCard" or "all"\) |
|
||||
| `limit` | number | No | Maximum number of actions to return \(default: 50, max: 1000\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the operation was successful |
|
||||
| `actions` | array | Array of action objects with type, date, member, and data |
|
||||
| `count` | number | Number of actions returned |
|
||||
| `error` | string | Error message if operation failed |
|
||||
|
||||
### `trello_add_comment`
|
||||
|
||||
Add a comment to a Trello card
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `cardId` | string | Yes | ID of the card to comment on |
|
||||
| `text` | string | Yes | Comment text |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the comment was added successfully |
|
||||
| `comment` | object | The created comment object with id, text, date, and member creator |
|
||||
| `error` | string | Error message if operation failed |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `trello`
|
||||
@@ -210,7 +210,8 @@ export async function GET(request: NextRequest) {
|
||||
displayName = `${acc.accountId} (${baseProvider})`
|
||||
}
|
||||
|
||||
const grantedScopes = acc.scope ? acc.scope.split(/[\s,]+/).filter(Boolean) : []
|
||||
const storedScope = acc.scope?.trim()
|
||||
const grantedScopes = storedScope ? storedScope.split(/[\s,]+/).filter(Boolean) : []
|
||||
const scopeEvaluation = evaluateScopeCoverage(acc.providerId, grantedScopes)
|
||||
|
||||
return {
|
||||
|
||||
@@ -200,7 +200,7 @@ describe('OAuth Token API Routes', () => {
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.status).toBe(404)
|
||||
expect(data).toHaveProperty('error')
|
||||
})
|
||||
|
||||
|
||||
@@ -71,10 +71,20 @@ export async function POST(request: NextRequest) {
|
||||
// Fetch the credential as the owner to enforce ownership scoping
|
||||
const credential = await getCredential(requestId, credentialId, authz.credentialOwnerUserId)
|
||||
|
||||
if (!credential) {
|
||||
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
try {
|
||||
// Refresh the token if needed
|
||||
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
|
||||
return NextResponse.json({ accessToken }, { status: 200 })
|
||||
return NextResponse.json(
|
||||
{
|
||||
accessToken,
|
||||
idToken: credential.idToken || undefined,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Failed to refresh access token:`, error)
|
||||
return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 })
|
||||
@@ -137,7 +147,13 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
|
||||
return NextResponse.json({ accessToken }, { status: 200 })
|
||||
return NextResponse.json(
|
||||
{
|
||||
accessToken,
|
||||
idToken: credential.idToken || undefined,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (_error) {
|
||||
return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 })
|
||||
}
|
||||
|
||||
41
apps/sim/app/api/auth/trello/authorize/route.ts
Normal file
41
apps/sim/app/api/auth/trello/authorize/route.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getBaseUrl } from '@/lib/urls/utils'
|
||||
|
||||
const logger = createLogger('TrelloAuthorize')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const apiKey = env.TRELLO_API_KEY
|
||||
|
||||
if (!apiKey) {
|
||||
logger.error('TRELLO_API_KEY not configured')
|
||||
return NextResponse.json({ error: 'Trello API key not configured' }, { status: 500 })
|
||||
}
|
||||
|
||||
const baseUrl = getBaseUrl()
|
||||
const returnUrl = `${baseUrl}/api/auth/trello/callback`
|
||||
|
||||
const authUrl = new URL('https://trello.com/1/authorize')
|
||||
authUrl.searchParams.set('key', apiKey)
|
||||
authUrl.searchParams.set('name', 'Sim Studio')
|
||||
authUrl.searchParams.set('expiration', 'never')
|
||||
authUrl.searchParams.set('response_type', 'token')
|
||||
authUrl.searchParams.set('scope', 'read,write')
|
||||
authUrl.searchParams.set('return_url', returnUrl)
|
||||
|
||||
return NextResponse.redirect(authUrl.toString())
|
||||
} catch (error) {
|
||||
logger.error('Error initiating Trello authorization:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
130
apps/sim/app/api/auth/trello/callback/route.ts
Normal file
130
apps/sim/app/api/auth/trello/callback/route.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getBaseUrl } from '@/lib/urls/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const baseUrl = getBaseUrl()
|
||||
|
||||
return new NextResponse(
|
||||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Connecting to Trello...</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #0052CC 0%, #0079BF 100%);
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #0052CC;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
.error {
|
||||
color: #ef4444;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
h2 {
|
||||
color: #111827;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
p {
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<h2>Connecting to Trello</h2>
|
||||
<p id="status">Processing authorization...</p>
|
||||
<p id="error" class="error" style="display:none;"></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const statusEl = document.getElementById('status');
|
||||
const errorEl = document.getElementById('error');
|
||||
|
||||
try {
|
||||
const fragment = window.location.hash.substring(1);
|
||||
const params = new URLSearchParams(fragment);
|
||||
const token = params.get('token');
|
||||
|
||||
if (!token) {
|
||||
throw new Error('No token received from Trello');
|
||||
}
|
||||
|
||||
statusEl.textContent = 'Saving your connection...';
|
||||
|
||||
fetch('${baseUrl}/api/auth/trello/store', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ token: token })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
statusEl.textContent = 'Success! Redirecting...';
|
||||
setTimeout(function() {
|
||||
window.location.href = '${baseUrl}/workspace?trello_connected=true';
|
||||
}, 500);
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to save connection');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
errorEl.textContent = error.message || 'Failed to save connection';
|
||||
errorEl.style.display = 'block';
|
||||
statusEl.textContent = 'Connection failed';
|
||||
setTimeout(function() {
|
||||
window.location.href = '${baseUrl}/workspace?error=trello_failed';
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
errorEl.textContent = error.message || 'Authorization failed';
|
||||
errorEl.style.display = 'block';
|
||||
statusEl.textContent = 'Connection failed';
|
||||
setTimeout(function() {
|
||||
window.location.href = '${baseUrl}/workspace?error=trello_auth_failed';
|
||||
}, 3000);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
87
apps/sim/app/api/auth/trello/store/route.ts
Normal file
87
apps/sim/app/api/auth/trello/store/route.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { db } from '@/../../packages/db'
|
||||
import { account } from '@/../../packages/db/schema'
|
||||
|
||||
const logger = createLogger('TrelloStore')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn('Unauthorized attempt to store Trello token')
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { token } = body
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json({ success: false, error: 'Token required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const apiKey = env.TRELLO_API_KEY
|
||||
if (!apiKey) {
|
||||
logger.error('TRELLO_API_KEY not configured')
|
||||
return NextResponse.json({ success: false, error: 'Trello not configured' }, { status: 500 })
|
||||
}
|
||||
|
||||
const validationUrl = `https://api.trello.com/1/members/me?key=${apiKey}&token=${token}&fields=id,username,fullName,email`
|
||||
const userResponse = await fetch(validationUrl, {
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
|
||||
if (!userResponse.ok) {
|
||||
const errorText = await userResponse.text()
|
||||
logger.error('Invalid Trello token', {
|
||||
status: userResponse.status,
|
||||
error: errorText,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `Invalid Trello token: ${errorText}` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const trelloUser = await userResponse.json()
|
||||
|
||||
const existing = await db.query.account.findFirst({
|
||||
where: and(eq(account.userId, session.user.id), eq(account.providerId, 'trello')),
|
||||
})
|
||||
|
||||
const now = new Date()
|
||||
|
||||
if (existing) {
|
||||
await db
|
||||
.update(account)
|
||||
.set({
|
||||
accessToken: token,
|
||||
accountId: trelloUser.id,
|
||||
scope: 'read,write',
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(account.id, existing.id))
|
||||
} else {
|
||||
await db.insert(account).values({
|
||||
id: `trello_${session.user.id}_${Date.now()}`,
|
||||
userId: session.user.id,
|
||||
providerId: 'trello',
|
||||
accountId: trelloUser.id,
|
||||
accessToken: token,
|
||||
scope: 'read,write',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
logger.error('Error storing Trello token:', error)
|
||||
return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
112
apps/sim/app/api/tools/asana/add-comment/route.ts
Normal file
112
apps/sim/app/api/tools/asana/add-comment/route.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateAlphanumericId } from '@/lib/security/input-validation'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AsanaAddCommentAPI')
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { accessToken, taskGid, text } = await request.json()
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!taskGid) {
|
||||
logger.error('Missing task GID in request')
|
||||
return NextResponse.json({ error: 'Task GID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
logger.error('Missing comment text in request')
|
||||
return NextResponse.json({ error: 'Comment text is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const taskGidValidation = validateAlphanumericId(taskGid, 'taskGid', 100)
|
||||
if (!taskGidValidation.isValid) {
|
||||
return NextResponse.json({ error: taskGidValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const url = `https://app.asana.com/api/1.0/tasks/${taskGid}/stories`
|
||||
|
||||
const body = {
|
||||
data: {
|
||||
text,
|
||||
},
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let errorMessage = `Asana API error: ${response.status} ${response.statusText}`
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
const asanaError = errorData.errors?.[0]
|
||||
if (asanaError) {
|
||||
errorMessage = `${asanaError.message || errorMessage} (${asanaError.help || ''})`
|
||||
}
|
||||
logger.error('Asana API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
})
|
||||
} catch (_e) {
|
||||
logger.error('Asana API error (unparsed):', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const story = result.data
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
gid: story.gid,
|
||||
text: story.text || '',
|
||||
created_at: story.created_at,
|
||||
created_by: story.created_by
|
||||
? {
|
||||
gid: story.created_by.gid,
|
||||
name: story.created_by.name,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error processing request:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to add comment to Asana task',
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
126
apps/sim/app/api/tools/asana/create-task/route.ts
Normal file
126
apps/sim/app/api/tools/asana/create-task/route.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateAlphanumericId } from '@/lib/security/input-validation'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AsanaCreateTaskAPI')
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { accessToken, workspace, name, notes, assignee, due_on } = await request.json()
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
logger.error('Missing task name in request')
|
||||
return NextResponse.json({ error: 'Task name is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
logger.error('Missing workspace in request')
|
||||
return NextResponse.json({ error: 'Workspace GID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const workspaceValidation = validateAlphanumericId(workspace, 'workspace', 100)
|
||||
if (!workspaceValidation.isValid) {
|
||||
return NextResponse.json({ error: workspaceValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const url = 'https://app.asana.com/api/1.0/tasks'
|
||||
|
||||
const taskData: Record<string, any> = {
|
||||
name,
|
||||
workspace,
|
||||
}
|
||||
|
||||
if (notes) {
|
||||
taskData.notes = notes
|
||||
}
|
||||
|
||||
if (assignee) {
|
||||
taskData.assignee = assignee
|
||||
}
|
||||
|
||||
if (due_on) {
|
||||
taskData.due_on = due_on
|
||||
}
|
||||
|
||||
const body = { data: taskData }
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let errorMessage = `Asana API error: ${response.status} ${response.statusText}`
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
const asanaError = errorData.errors?.[0]
|
||||
if (asanaError) {
|
||||
errorMessage = `${asanaError.message || errorMessage} (${asanaError.help || ''})`
|
||||
}
|
||||
logger.error('Asana API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
})
|
||||
} catch (_e) {
|
||||
logger.error('Asana API error (unparsed):', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const task = result.data
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
gid: task.gid,
|
||||
name: task.name,
|
||||
notes: task.notes || '',
|
||||
completed: task.completed || false,
|
||||
created_at: task.created_at,
|
||||
permalink_url: task.permalink_url,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating Asana task:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
95
apps/sim/app/api/tools/asana/get-projects/route.ts
Normal file
95
apps/sim/app/api/tools/asana/get-projects/route.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateAlphanumericId } from '@/lib/security/input-validation'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AsanaGetProjectsAPI')
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { accessToken, workspace } = await request.json()
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
logger.error('Missing workspace in request')
|
||||
return NextResponse.json({ error: 'Workspace is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const workspaceValidation = validateAlphanumericId(workspace, 'workspace', 100)
|
||||
if (!workspaceValidation.isValid) {
|
||||
return NextResponse.json({ error: workspaceValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const url = `https://app.asana.com/api/1.0/projects?workspace=${workspace}`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let errorMessage = `Asana API error: ${response.status} ${response.statusText}`
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
const asanaError = errorData.errors?.[0]
|
||||
if (asanaError) {
|
||||
errorMessage = `${asanaError.message || errorMessage} (${asanaError.help || ''})`
|
||||
}
|
||||
logger.error('Asana API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
})
|
||||
} catch (_e) {
|
||||
logger.error('Asana API error (unparsed):', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const projects = result.data
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
projects: projects.map((project: any) => ({
|
||||
gid: project.gid,
|
||||
name: project.name,
|
||||
resource_type: project.resource_type,
|
||||
})),
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error processing request:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to retrieve Asana projects',
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
222
apps/sim/app/api/tools/asana/get-task/route.ts
Normal file
222
apps/sim/app/api/tools/asana/get-task/route.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateAlphanumericId } from '@/lib/security/input-validation'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AsanaGetTaskAPI')
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { accessToken, taskGid, workspace, project, limit } = await request.json()
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (taskGid) {
|
||||
const taskGidValidation = validateAlphanumericId(taskGid, 'taskGid', 100)
|
||||
if (!taskGidValidation.isValid) {
|
||||
return NextResponse.json({ error: taskGidValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const url = `https://app.asana.com/api/1.0/tasks/${taskGid}?opt_fields=gid,name,notes,completed,assignee,assignee.name,due_on,created_at,modified_at,created_by,created_by.name,resource_type,resource_subtype`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let errorMessage = `Asana API error: ${response.status} ${response.statusText}`
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
const asanaError = errorData.errors?.[0]
|
||||
if (asanaError) {
|
||||
errorMessage = `${asanaError.message || errorMessage} (${asanaError.help || ''})`
|
||||
}
|
||||
logger.error('Asana API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
})
|
||||
} catch (_e) {
|
||||
logger.error('Asana API error (unparsed):', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const task = result.data
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
gid: task.gid,
|
||||
resource_type: task.resource_type,
|
||||
resource_subtype: task.resource_subtype,
|
||||
name: task.name,
|
||||
notes: task.notes || '',
|
||||
completed: task.completed || false,
|
||||
assignee: task.assignee
|
||||
? {
|
||||
gid: task.assignee.gid,
|
||||
name: task.assignee.name,
|
||||
}
|
||||
: undefined,
|
||||
created_by: task.created_by
|
||||
? {
|
||||
gid: task.created_by.gid,
|
||||
resource_type: task.created_by.resource_type,
|
||||
name: task.created_by.name,
|
||||
}
|
||||
: undefined,
|
||||
due_on: task.due_on || undefined,
|
||||
created_at: task.created_at,
|
||||
modified_at: task.modified_at,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (!workspace && !project) {
|
||||
logger.error('Either taskGid or workspace/project must be provided')
|
||||
return NextResponse.json(
|
||||
{ error: 'Either taskGid or workspace/project must be provided' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (project) {
|
||||
const projectValidation = validateAlphanumericId(project, 'project', 100)
|
||||
if (!projectValidation.isValid) {
|
||||
return NextResponse.json({ error: projectValidation.error }, { status: 400 })
|
||||
}
|
||||
params.append('project', project)
|
||||
} else if (workspace) {
|
||||
const workspaceValidation = validateAlphanumericId(workspace, 'workspace', 100)
|
||||
if (!workspaceValidation.isValid) {
|
||||
return NextResponse.json({ error: workspaceValidation.error }, { status: 400 })
|
||||
}
|
||||
params.append('workspace', workspace)
|
||||
}
|
||||
|
||||
if (limit) {
|
||||
params.append('limit', String(limit))
|
||||
} else {
|
||||
params.append('limit', '50')
|
||||
}
|
||||
|
||||
params.append(
|
||||
'opt_fields',
|
||||
'gid,name,notes,completed,assignee,assignee.name,due_on,created_at,modified_at,created_by,created_by.name,resource_type,resource_subtype'
|
||||
)
|
||||
|
||||
const url = `https://app.asana.com/api/1.0/tasks?${params.toString()}`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let errorMessage = `Asana API error: ${response.status} ${response.statusText}`
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
const asanaError = errorData.errors?.[0]
|
||||
if (asanaError) {
|
||||
errorMessage = `${asanaError.message || errorMessage} (${asanaError.help || ''})`
|
||||
}
|
||||
logger.error('Asana API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
})
|
||||
} catch (_e) {
|
||||
logger.error('Asana API error (unparsed):', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const tasks = result.data
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
tasks: tasks.map((task: any) => ({
|
||||
gid: task.gid,
|
||||
resource_type: task.resource_type,
|
||||
resource_subtype: task.resource_subtype,
|
||||
name: task.name,
|
||||
notes: task.notes || '',
|
||||
completed: task.completed || false,
|
||||
assignee: task.assignee
|
||||
? {
|
||||
gid: task.assignee.gid,
|
||||
name: task.assignee.name,
|
||||
}
|
||||
: undefined,
|
||||
created_by: task.created_by
|
||||
? {
|
||||
gid: task.created_by.gid,
|
||||
resource_type: task.created_by.resource_type,
|
||||
name: task.created_by.name,
|
||||
}
|
||||
: undefined,
|
||||
due_on: task.due_on || undefined,
|
||||
created_at: task.created_at,
|
||||
modified_at: task.modified_at,
|
||||
})),
|
||||
next_page: result.next_page,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error processing request:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to retrieve Asana task(s)',
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
138
apps/sim/app/api/tools/asana/search-tasks/route.ts
Normal file
138
apps/sim/app/api/tools/asana/search-tasks/route.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateAlphanumericId } from '@/lib/security/input-validation'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AsanaSearchTasksAPI')
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { accessToken, workspace, text, assignee, projects, completed } = await request.json()
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
logger.error('Missing workspace in request')
|
||||
return NextResponse.json({ error: 'Workspace is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const workspaceValidation = validateAlphanumericId(workspace, 'workspace', 100)
|
||||
if (!workspaceValidation.isValid) {
|
||||
return NextResponse.json({ error: workspaceValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (text) {
|
||||
params.append('text', text)
|
||||
}
|
||||
|
||||
if (assignee) {
|
||||
params.append('assignee.any', assignee)
|
||||
}
|
||||
|
||||
if (projects && Array.isArray(projects) && projects.length > 0) {
|
||||
params.append('projects.any', projects.join(','))
|
||||
}
|
||||
|
||||
if (completed !== undefined) {
|
||||
params.append('completed', String(completed))
|
||||
}
|
||||
|
||||
params.append(
|
||||
'opt_fields',
|
||||
'gid,name,notes,completed,assignee,assignee.name,due_on,created_at,modified_at,created_by,created_by.name,resource_type,resource_subtype'
|
||||
)
|
||||
|
||||
const url = `https://app.asana.com/api/1.0/workspaces/${workspace}/tasks/search?${params.toString()}`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let errorMessage = `Asana API error: ${response.status} ${response.statusText}`
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
const asanaError = errorData.errors?.[0]
|
||||
if (asanaError) {
|
||||
errorMessage = `${asanaError.message || errorMessage} (${asanaError.help || ''})`
|
||||
}
|
||||
logger.error('Asana API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
})
|
||||
} catch (_e) {
|
||||
logger.error('Asana API error (unparsed):', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const tasks = result.data
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
tasks: tasks.map((task: any) => ({
|
||||
gid: task.gid,
|
||||
resource_type: task.resource_type,
|
||||
resource_subtype: task.resource_subtype,
|
||||
name: task.name,
|
||||
notes: task.notes || '',
|
||||
completed: task.completed || false,
|
||||
assignee: task.assignee
|
||||
? {
|
||||
gid: task.assignee.gid,
|
||||
name: task.assignee.name,
|
||||
}
|
||||
: undefined,
|
||||
created_by: task.created_by
|
||||
? {
|
||||
gid: task.created_by.gid,
|
||||
resource_type: task.created_by.resource_type,
|
||||
name: task.created_by.name,
|
||||
}
|
||||
: undefined,
|
||||
due_on: task.due_on || undefined,
|
||||
created_at: task.created_at,
|
||||
modified_at: task.modified_at,
|
||||
})),
|
||||
next_page: result.next_page,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error processing request:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to search Asana tasks',
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
125
apps/sim/app/api/tools/asana/update-task/route.ts
Normal file
125
apps/sim/app/api/tools/asana/update-task/route.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateAlphanumericId } from '@/lib/security/input-validation'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AsanaUpdateTaskAPI')
|
||||
|
||||
export async function PUT(request: Request) {
|
||||
try {
|
||||
const { accessToken, taskGid, name, notes, assignee, completed, due_on } = await request.json()
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!taskGid) {
|
||||
logger.error('Missing task GID in request')
|
||||
return NextResponse.json({ error: 'Task GID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const taskGidValidation = validateAlphanumericId(taskGid, 'taskGid', 100)
|
||||
if (!taskGidValidation.isValid) {
|
||||
return NextResponse.json({ error: taskGidValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const url = `https://app.asana.com/api/1.0/tasks/${taskGid}`
|
||||
|
||||
const taskData: Record<string, any> = {}
|
||||
|
||||
if (name !== undefined) {
|
||||
taskData.name = name
|
||||
}
|
||||
|
||||
if (notes !== undefined) {
|
||||
taskData.notes = notes
|
||||
}
|
||||
|
||||
if (assignee !== undefined) {
|
||||
taskData.assignee = assignee
|
||||
}
|
||||
|
||||
if (completed !== undefined) {
|
||||
taskData.completed = completed
|
||||
}
|
||||
|
||||
if (due_on !== undefined) {
|
||||
taskData.due_on = due_on
|
||||
}
|
||||
|
||||
const body = { data: taskData }
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let errorMessage = `Asana API error: ${response.status} ${response.statusText}`
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
const asanaError = errorData.errors?.[0]
|
||||
if (asanaError) {
|
||||
errorMessage = `${asanaError.message || errorMessage} (${asanaError.help || ''})`
|
||||
}
|
||||
logger.error('Asana API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorData,
|
||||
})
|
||||
} catch (_e) {
|
||||
logger.error('Asana API error (unparsed):', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const task = result.data
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
gid: task.gid,
|
||||
name: task.name,
|
||||
notes: task.notes || '',
|
||||
completed: task.completed || false,
|
||||
modified_at: task.modified_at,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating Asana task:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,13 @@ export async function POST(
|
||||
const requestId = generateRequestId()
|
||||
const { path } = await params
|
||||
|
||||
// Log ALL incoming webhook requests for debugging
|
||||
logger.info(`[${requestId}] Incoming webhook request`, {
|
||||
path,
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
})
|
||||
|
||||
// Handle Microsoft Graph subscription validation (some environments send POST with validationToken)
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
@@ -91,6 +98,23 @@ export async function POST(
|
||||
|
||||
const { webhook: foundWebhook, workflow: foundWorkflow } = findResult
|
||||
|
||||
// Log HubSpot webhook details for debugging
|
||||
if (foundWebhook.provider === 'hubspot') {
|
||||
const events = Array.isArray(body) ? body : [body]
|
||||
const firstEvent = events[0]
|
||||
|
||||
logger.info(`[${requestId}] HubSpot webhook received`, {
|
||||
path,
|
||||
subscriptionType: firstEvent?.subscriptionType,
|
||||
objectId: firstEvent?.objectId,
|
||||
portalId: firstEvent?.portalId,
|
||||
webhookId: foundWebhook.id,
|
||||
workflowId: foundWorkflow.id,
|
||||
triggerId: foundWebhook.providerConfig?.triggerId,
|
||||
eventCount: events.length,
|
||||
})
|
||||
}
|
||||
|
||||
const authError = await verifyProviderAuth(
|
||||
foundWebhook,
|
||||
foundWorkflow,
|
||||
|
||||
@@ -51,6 +51,7 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
||||
'write:confluence-content': 'Create and edit Confluence pages',
|
||||
'write:confluence-space': 'Manage Confluence spaces',
|
||||
'write:confluence-file': 'Upload files to Confluence',
|
||||
'read:content:confluence': 'Read Confluence content',
|
||||
'read:page:confluence': 'View Confluence pages',
|
||||
'write:page:confluence': 'Create and update Confluence pages',
|
||||
'read:comment:confluence': 'View comments on Confluence pages',
|
||||
@@ -189,6 +190,47 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
||||
'sites:write': 'Manage webhooks and site settings',
|
||||
'cms:read': 'View your CMS content',
|
||||
'cms:write': 'Manage your CMS content',
|
||||
'crm.objects.contacts.read': 'Read your HubSpot contacts',
|
||||
'crm.objects.contacts.write': 'Create and update HubSpot contacts',
|
||||
'crm.objects.companies.read': 'Read your HubSpot companies',
|
||||
'crm.objects.companies.write': 'Create and update HubSpot companies',
|
||||
'crm.objects.deals.read': 'Read your HubSpot deals',
|
||||
'crm.objects.deals.write': 'Create and update HubSpot deals',
|
||||
'crm.objects.owners.read': 'Read HubSpot object owners',
|
||||
'crm.objects.users.read': 'Read HubSpot users',
|
||||
'crm.objects.users.write': 'Create and update HubSpot users',
|
||||
'crm.objects.marketing_events.read': 'Read HubSpot marketing events',
|
||||
'crm.objects.marketing_events.write': 'Create and update HubSpot marketing events',
|
||||
'crm.objects.line_items.read': 'Read HubSpot line items',
|
||||
'crm.objects.line_items.write': 'Create and update HubSpot line items',
|
||||
'crm.objects.quotes.read': 'Read HubSpot quotes',
|
||||
'crm.objects.quotes.write': 'Create and update HubSpot quotes',
|
||||
'crm.objects.appointments.read': 'Read HubSpot appointments',
|
||||
'crm.objects.appointments.write': 'Create and update HubSpot appointments',
|
||||
'crm.objects.carts.read': 'Read HubSpot shopping carts',
|
||||
'crm.objects.carts.write': 'Create and update HubSpot shopping carts',
|
||||
'crm.import': 'Import data into HubSpot',
|
||||
'crm.lists.read': 'Read HubSpot lists',
|
||||
'crm.lists.write': 'Create and update HubSpot lists',
|
||||
tickets: 'Manage HubSpot tickets',
|
||||
api: 'Access Salesforce API',
|
||||
refresh_token: 'Maintain long-term access to your Salesforce account',
|
||||
default: 'Access your Asana workspace',
|
||||
base: 'Basic access to your Pipedrive account',
|
||||
'deals:read': 'Read your Pipedrive deals',
|
||||
'deals:full': 'Full access to manage your Pipedrive deals',
|
||||
'contacts:read': 'Read your Pipedrive contacts',
|
||||
'contacts:full': 'Full access to manage your Pipedrive contacts',
|
||||
'leads:read': 'Read your Pipedrive leads',
|
||||
'leads:full': 'Full access to manage your Pipedrive leads',
|
||||
'activities:read': 'Read your Pipedrive activities',
|
||||
'activities:full': 'Full access to manage your Pipedrive activities',
|
||||
'mail:read': 'Read your Pipedrive emails',
|
||||
'mail:full': 'Full access to manage your Pipedrive emails',
|
||||
'projects:read': 'Read your Pipedrive projects',
|
||||
'projects:full': 'Full access to manage your Pipedrive projects',
|
||||
'webhooks:read': 'Read your Pipedrive webhooks',
|
||||
'webhooks:full': 'Full access to manage your Pipedrive webhooks',
|
||||
}
|
||||
|
||||
function getScopeDescription(scope: string): string {
|
||||
@@ -241,6 +283,11 @@ export function OAuthRequiredModal({
|
||||
requiredScopes,
|
||||
})
|
||||
|
||||
if (providerId === 'trello') {
|
||||
window.location.href = '/api/auth/trello/authorize'
|
||||
return
|
||||
}
|
||||
|
||||
await client.oauth2.link({
|
||||
providerId,
|
||||
callbackURL: window.location.href,
|
||||
@@ -278,7 +325,7 @@ export function OAuthRequiredModal({
|
||||
<div className='border-b px-4 py-3'>
|
||||
<h4 className='font-medium text-sm'>Permissions requested</h4>
|
||||
</div>
|
||||
<ul className='space-y-3 px-4 py-3'>
|
||||
<ul className='max-h-[400px] space-y-3 overflow-y-auto px-4 py-3'>
|
||||
{displayScopes.map((scope) => (
|
||||
<li key={scope} className='flex items-start gap-2 text-sm'>
|
||||
<div className='mt-1 rounded-full bg-muted p-0.5'>
|
||||
|
||||
@@ -237,6 +237,11 @@ export function Credentials({ onOpenChange, registerCloseHandler }: CredentialsP
|
||||
scopes: service.scopes,
|
||||
})
|
||||
|
||||
if (service.providerId === 'trello') {
|
||||
window.location.href = '/api/auth/trello/authorize'
|
||||
return
|
||||
}
|
||||
|
||||
await client.oauth2.link({
|
||||
providerId: service.providerId,
|
||||
callbackURL: window.location.href,
|
||||
|
||||
@@ -34,7 +34,12 @@ export const AirtableBlock: BlockConfig<AirtableResponse> = {
|
||||
type: 'oauth-input',
|
||||
provider: 'airtable',
|
||||
serviceId: 'airtable',
|
||||
requiredScopes: ['data.records:read', 'data.records:write'], // Keep both scopes
|
||||
requiredScopes: [
|
||||
'data.records:read',
|
||||
'data.records:write',
|
||||
'user.email:read',
|
||||
'webhook:manage',
|
||||
],
|
||||
placeholder: 'Select Airtable account',
|
||||
required: true,
|
||||
},
|
||||
|
||||
293
apps/sim/blocks/blocks/asana.ts
Normal file
293
apps/sim/blocks/blocks/asana.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
import { AsanaIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { AsanaResponse } from '@/tools/asana/types'
|
||||
|
||||
export const AsanaBlock: BlockConfig<AsanaResponse> = {
|
||||
type: 'asana',
|
||||
name: 'Asana',
|
||||
description: 'Interact with Asana',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription: 'Integrate Asana into the workflow. Can read, write, and update tasks.',
|
||||
docsLink: 'https://docs.sim.ai/tools/asana',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: AsanaIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Get Task', id: 'get_task' },
|
||||
{ label: 'Create Task', id: 'create_task' },
|
||||
{ label: 'Update Task', id: 'update_task' },
|
||||
{ label: 'Get Projects', id: 'get_projects' },
|
||||
{ label: 'Search Tasks', id: 'search_tasks' },
|
||||
{ label: 'Add Comment', id: 'add_comment' },
|
||||
],
|
||||
value: () => 'get_task',
|
||||
},
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Asana Account',
|
||||
type: 'oauth-input',
|
||||
|
||||
required: true,
|
||||
provider: 'asana',
|
||||
serviceId: 'asana',
|
||||
requiredScopes: ['default'],
|
||||
placeholder: 'Select Asana account',
|
||||
},
|
||||
{
|
||||
id: 'workspace',
|
||||
title: 'Workspace GID',
|
||||
type: 'short-input',
|
||||
required: true,
|
||||
placeholder: 'Enter Asana workspace GID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_task', 'get_projects', 'search_tasks'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'taskGid',
|
||||
title: 'Task GID',
|
||||
type: 'short-input',
|
||||
required: false,
|
||||
placeholder: 'Leave empty to get all tasks with filters below',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'taskGid',
|
||||
title: 'Task GID',
|
||||
type: 'short-input',
|
||||
required: true,
|
||||
placeholder: 'Enter Asana task GID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['update_task', 'add_comment'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'getTasks_workspace',
|
||||
title: 'Workspace GID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter workspace GID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'getTasks_project',
|
||||
title: 'Project GID',
|
||||
type: 'short-input',
|
||||
|
||||
placeholder: 'Enter project GID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'getTasks_limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
|
||||
placeholder: 'Max tasks to return (default: 50)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
title: 'Task Name',
|
||||
type: 'short-input',
|
||||
|
||||
required: true,
|
||||
placeholder: 'Enter task name',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_task', 'update_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'notes',
|
||||
title: 'Task Notes',
|
||||
type: 'long-input',
|
||||
|
||||
placeholder: 'Enter task notes or description',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_task', 'update_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'assignee',
|
||||
title: 'Assignee GID',
|
||||
type: 'short-input',
|
||||
|
||||
placeholder: 'Enter assignee user GID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_task', 'update_task', 'search_tasks'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'due_on',
|
||||
title: 'Due Date',
|
||||
type: 'short-input',
|
||||
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_task', 'update_task'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'searchText',
|
||||
title: 'Search Text',
|
||||
type: 'short-input',
|
||||
|
||||
placeholder: 'Enter search text',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['search_tasks'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'commentText',
|
||||
title: 'Comment Text',
|
||||
type: 'long-input',
|
||||
|
||||
required: true,
|
||||
placeholder: 'Enter comment text',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['add_comment'],
|
||||
},
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'asana_get_task',
|
||||
'asana_create_task',
|
||||
'asana_update_task',
|
||||
'asana_get_projects',
|
||||
'asana_search_tasks',
|
||||
'asana_add_comment',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'get_task':
|
||||
return 'asana_get_task'
|
||||
case 'create_task':
|
||||
return 'asana_create_task'
|
||||
case 'update_task':
|
||||
return 'asana_update_task'
|
||||
case 'get_projects':
|
||||
return 'asana_get_projects'
|
||||
case 'search_tasks':
|
||||
return 'asana_search_tasks'
|
||||
case 'add_comment':
|
||||
return 'asana_add_comment'
|
||||
default:
|
||||
return 'asana_get_task'
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { credential, operation } = params
|
||||
|
||||
const projectsArray = params.projects
|
||||
? params.projects
|
||||
.split(',')
|
||||
.map((p: string) => p.trim())
|
||||
.filter((p: string) => p.length > 0)
|
||||
: undefined
|
||||
|
||||
const baseParams = {
|
||||
accessToken: credential?.accessToken,
|
||||
}
|
||||
|
||||
switch (operation) {
|
||||
case 'get_task':
|
||||
return {
|
||||
...baseParams,
|
||||
taskGid: params.taskGid,
|
||||
workspace: params.getTasks_workspace,
|
||||
project: params.getTasks_project,
|
||||
limit: params.getTasks_limit ? Number(params.getTasks_limit) : undefined,
|
||||
}
|
||||
case 'create_task':
|
||||
return {
|
||||
...baseParams,
|
||||
workspace: params.workspace,
|
||||
name: params.name,
|
||||
notes: params.notes,
|
||||
assignee: params.assignee,
|
||||
due_on: params.due_on,
|
||||
}
|
||||
case 'update_task':
|
||||
return {
|
||||
...baseParams,
|
||||
taskGid: params.taskGid,
|
||||
name: params.name,
|
||||
notes: params.notes,
|
||||
assignee: params.assignee,
|
||||
completed: params.completed?.includes('completed'),
|
||||
due_on: params.due_on,
|
||||
}
|
||||
case 'get_projects':
|
||||
return {
|
||||
...baseParams,
|
||||
workspace: params.workspace,
|
||||
}
|
||||
case 'search_tasks':
|
||||
return {
|
||||
...baseParams,
|
||||
workspace: params.workspace,
|
||||
text: params.searchText,
|
||||
assignee: params.assignee,
|
||||
projects: projectsArray,
|
||||
completed: params.completed?.includes('completed'),
|
||||
}
|
||||
case 'add_comment':
|
||||
return {
|
||||
...baseParams,
|
||||
taskGid: params.taskGid,
|
||||
text: params.commentText,
|
||||
}
|
||||
default:
|
||||
return baseParams
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
workspace: { type: 'string', description: 'Workspace GID' },
|
||||
taskGid: { type: 'string', description: 'Task GID' },
|
||||
getTasks_workspace: { type: 'string', description: 'Workspace GID for getting tasks' },
|
||||
getTasks_project: { type: 'string', description: 'Project GID filter for getting tasks' },
|
||||
getTasks_limit: { type: 'string', description: 'Limit for getting tasks' },
|
||||
name: { type: 'string', description: 'Task name' },
|
||||
notes: { type: 'string', description: 'Task notes' },
|
||||
assignee: { type: 'string', description: 'Assignee user GID' },
|
||||
due_on: { type: 'string', description: 'Due date (YYYY-MM-DD)' },
|
||||
projects: { type: 'string', description: 'Project GIDs' },
|
||||
completed: { type: 'array', description: 'Completion status' },
|
||||
searchText: { type: 'string', description: 'Search text' },
|
||||
commentText: { type: 'string', description: 'Comment text' },
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: { type: 'string', description: 'Operation result (JSON)' },
|
||||
},
|
||||
}
|
||||
@@ -118,7 +118,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
|
||||
id: 'threadId',
|
||||
title: 'Thread ID',
|
||||
type: 'short-input',
|
||||
|
||||
placeholder: 'Thread ID to reply to (for threading)',
|
||||
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
|
||||
mode: 'advanced',
|
||||
@@ -128,7 +127,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
|
||||
id: 'replyToMessageId',
|
||||
title: 'Reply to Message ID',
|
||||
type: 'short-input',
|
||||
|
||||
placeholder: 'Gmail message ID (not RFC Message-ID) - use the "id" field from results',
|
||||
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
|
||||
mode: 'advanced',
|
||||
@@ -233,7 +231,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
|
||||
id: 'destinationLabel',
|
||||
title: 'Move To Label',
|
||||
type: 'folder-selector',
|
||||
|
||||
canonicalParamId: 'addLabelIds',
|
||||
provider: 'google-email',
|
||||
serviceId: 'gmail',
|
||||
@@ -249,7 +246,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
|
||||
id: 'manualDestinationLabel',
|
||||
title: 'Move To Label',
|
||||
type: 'short-input',
|
||||
|
||||
canonicalParamId: 'addLabelIds',
|
||||
placeholder: 'Enter label ID (e.g., INBOX, Label_123)',
|
||||
mode: 'advanced',
|
||||
|
||||
@@ -20,7 +20,11 @@ export const GoogleFormsBlock: BlockConfig = {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
serviceId: 'google-forms',
|
||||
requiredScopes: [],
|
||||
requiredScopes: [
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
'https://www.googleapis.com/auth/userinfo.profile',
|
||||
'https://www.googleapis.com/auth/forms.responses.readonly',
|
||||
],
|
||||
placeholder: 'Select Google account',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -36,7 +36,10 @@ export const GoogleSheetsBlock: BlockConfig<GoogleSheetsResponse> = {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
serviceId: 'google-sheets',
|
||||
requiredScopes: [],
|
||||
requiredScopes: [
|
||||
'https://www.googleapis.com/auth/drive.readonly',
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
],
|
||||
placeholder: 'Select Google account',
|
||||
},
|
||||
// Spreadsheet Selector
|
||||
@@ -47,7 +50,10 @@ export const GoogleSheetsBlock: BlockConfig<GoogleSheetsResponse> = {
|
||||
canonicalParamId: 'spreadsheetId',
|
||||
provider: 'google-drive',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [],
|
||||
requiredScopes: [
|
||||
'https://www.googleapis.com/auth/drive.readonly',
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
],
|
||||
mimeType: 'application/vnd.google-apps.spreadsheet',
|
||||
placeholder: 'Select a spreadsheet',
|
||||
dependsOn: ['credential'],
|
||||
|
||||
981
apps/sim/blocks/blocks/hubspot.ts
Normal file
981
apps/sim/blocks/blocks/hubspot.ts
Normal file
@@ -0,0 +1,981 @@
|
||||
import { HubspotIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { HubSpotResponse } from '@/tools/hubspot/types'
|
||||
import { getTrigger } from '@/triggers'
|
||||
import { hubspotAllTriggerOptions } from '@/triggers/hubspot/utils'
|
||||
|
||||
export const HubSpotBlock: BlockConfig<HubSpotResponse> = {
|
||||
type: 'hubspot',
|
||||
name: 'HubSpot',
|
||||
description: 'Interact with HubSpot CRM or trigger workflows from HubSpot events',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate HubSpot into your workflow. Manage contacts, companies, deals, tickets, and other CRM objects with powerful automation capabilities. Can be used in trigger mode to start workflows when contacts are created, deleted, or updated.',
|
||||
docsLink: 'https://docs.sim.ai/tools/hubspot',
|
||||
category: 'tools',
|
||||
bgColor: '#FF7A59',
|
||||
icon: HubspotIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Get Users', id: 'get_users' },
|
||||
{ label: 'Get Contacts', id: 'get_contacts' },
|
||||
{ label: 'Create Contact', id: 'create_contact' },
|
||||
{ label: 'Update Contact', id: 'update_contact' },
|
||||
{ label: 'Search Contacts', id: 'search_contacts' },
|
||||
{ label: 'Get Companies', id: 'get_companies' },
|
||||
{ label: 'Create Company', id: 'create_company' },
|
||||
{ label: 'Update Company', id: 'update_company' },
|
||||
{ label: 'Search Companies', id: 'search_companies' },
|
||||
{ label: 'Get Deals', id: 'get_deals' },
|
||||
],
|
||||
value: () => 'get_contacts',
|
||||
},
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'HubSpot Account',
|
||||
type: 'oauth-input',
|
||||
provider: 'hubspot',
|
||||
serviceId: 'hubspot',
|
||||
requiredScopes: [
|
||||
'crm.objects.contacts.read',
|
||||
'crm.objects.contacts.write',
|
||||
'crm.objects.companies.read',
|
||||
'crm.objects.companies.write',
|
||||
'crm.objects.deals.read',
|
||||
'crm.objects.deals.write',
|
||||
'crm.objects.owners.read',
|
||||
'crm.objects.users.read',
|
||||
'crm.objects.users.write',
|
||||
'crm.objects.marketing_events.read',
|
||||
'crm.objects.marketing_events.write',
|
||||
'crm.objects.line_items.read',
|
||||
'crm.objects.line_items.write',
|
||||
'crm.objects.quotes.read',
|
||||
'crm.objects.quotes.write',
|
||||
'crm.objects.appointments.read',
|
||||
'crm.objects.appointments.write',
|
||||
'crm.objects.carts.read',
|
||||
'crm.objects.carts.write',
|
||||
'crm.import',
|
||||
'crm.lists.read',
|
||||
'crm.lists.write',
|
||||
'tickets',
|
||||
],
|
||||
placeholder: 'Select HubSpot account',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'contactId',
|
||||
title: 'Contact ID or Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional - Leave empty to list all contacts',
|
||||
condition: { field: 'operation', value: ['get_contacts', 'update_contact'] },
|
||||
},
|
||||
{
|
||||
id: 'companyId',
|
||||
title: 'Company ID or Domain',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional - Leave empty to list all companies',
|
||||
condition: { field: 'operation', value: ['get_companies', 'update_company'] },
|
||||
},
|
||||
{
|
||||
id: 'idProperty',
|
||||
title: 'ID Property',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional - e.g., "email" for contacts, "domain" for companies',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_contacts', 'update_contact', 'get_companies', 'update_company'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'propertiesToSet',
|
||||
title: 'Properties',
|
||||
type: 'long-input',
|
||||
placeholder:
|
||||
'JSON object with properties (e.g., {"email": "test@example.com", "firstname": "John"})',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_contact', 'update_contact', 'create_company', 'update_company'],
|
||||
},
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert HubSpot CRM developer. Generate HubSpot property objects as JSON based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the JSON object with HubSpot properties. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON object that can be used directly in HubSpot API create/update operations.
|
||||
|
||||
### HUBSPOT PROPERTIES STRUCTURE
|
||||
HubSpot properties are defined as a flat JSON object with property names as keys and their values as the corresponding values. Property names must match HubSpot's internal property names (usually lowercase, snake_case or no spaces).
|
||||
|
||||
### COMMON CONTACT PROPERTIES
|
||||
**Standard Properties**:
|
||||
- **email**: Email address (required for most operations)
|
||||
- **firstname**: First name
|
||||
- **lastname**: Last name
|
||||
- **phone**: Phone number
|
||||
- **mobilephone**: Mobile phone number
|
||||
- **company**: Company name
|
||||
- **jobtitle**: Job title
|
||||
- **website**: Website URL
|
||||
- **address**: Street address
|
||||
- **city**: City
|
||||
- **state**: State/Region
|
||||
- **zip**: Postal code
|
||||
- **country**: Country
|
||||
- **lifecyclestage**: Lifecycle stage (e.g., "lead", "customer", "subscriber", "opportunity")
|
||||
- **hs_lead_status**: Lead status (e.g., "NEW", "OPEN", "IN_PROGRESS", "QUALIFIED")
|
||||
|
||||
**Additional Properties**:
|
||||
- **salutation**: Salutation (e.g., "Mr.", "Ms.", "Dr.")
|
||||
- **degree**: Degree
|
||||
- **industry**: Industry
|
||||
- **fax**: Fax number
|
||||
- **numemployees**: Number of employees (for companies)
|
||||
- **annualrevenue**: Annual revenue (for companies)
|
||||
|
||||
### COMMON COMPANY PROPERTIES
|
||||
**Standard Properties**:
|
||||
- **name**: Company name (required)
|
||||
- **domain**: Company domain (e.g., "example.com")
|
||||
- **city**: City
|
||||
- **state**: State/Region
|
||||
- **zip**: Postal code
|
||||
- **country**: Country
|
||||
- **phone**: Phone number
|
||||
- **industry**: Industry
|
||||
- **type**: Company type (e.g., "PROSPECT", "PARTNER", "RESELLER", "VENDOR", "OTHER")
|
||||
- **description**: Company description
|
||||
- **website**: Website URL
|
||||
- **numberofemployees**: Number of employees
|
||||
- **annualrevenue**: Annual revenue
|
||||
|
||||
**Additional Properties**:
|
||||
- **timezone**: Timezone
|
||||
- **linkedin_company_page**: LinkedIn URL
|
||||
- **twitterhandle**: Twitter handle
|
||||
- **facebook_company_page**: Facebook URL
|
||||
- **founded_year**: Year founded
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Simple Contact**: "Create contact with email john@example.com and name John Doe"
|
||||
→ {
|
||||
"email": "john@example.com",
|
||||
"firstname": "John",
|
||||
"lastname": "Doe"
|
||||
}
|
||||
|
||||
**Complete Contact**: "Create a lead contact with full details"
|
||||
→ {
|
||||
"email": "jane.smith@acme.com",
|
||||
"firstname": "Jane",
|
||||
"lastname": "Smith",
|
||||
"phone": "+1-555-123-4567",
|
||||
"company": "Acme Corp",
|
||||
"jobtitle": "Marketing Manager",
|
||||
"website": "https://acme.com",
|
||||
"city": "San Francisco",
|
||||
"state": "California",
|
||||
"country": "United States",
|
||||
"lifecyclestage": "lead",
|
||||
"hs_lead_status": "NEW"
|
||||
}
|
||||
|
||||
**Simple Company**: "Create company Acme Corp with domain acme.com"
|
||||
→ {
|
||||
"name": "Acme Corp",
|
||||
"domain": "acme.com"
|
||||
}
|
||||
|
||||
**Complete Company**: "Create a technology company with full details"
|
||||
→ {
|
||||
"name": "TechStart Inc",
|
||||
"domain": "techstart.io",
|
||||
"industry": "TECHNOLOGY",
|
||||
"phone": "+1-555-987-6543",
|
||||
"city": "Austin",
|
||||
"state": "Texas",
|
||||
"country": "United States",
|
||||
"website": "https://techstart.io",
|
||||
"description": "Innovative software solutions",
|
||||
"numberofemployees": 50,
|
||||
"annualrevenue": 5000000,
|
||||
"type": "PROSPECT"
|
||||
}
|
||||
|
||||
**Update Contact**: "Update contact phone and job title"
|
||||
→ {
|
||||
"phone": "+1-555-999-8888",
|
||||
"jobtitle": "Senior Manager"
|
||||
}
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the JSON object with properties - no explanations, no markdown, no extra text.`,
|
||||
placeholder: 'Describe the properties you want to set...',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'properties',
|
||||
title: 'Properties to Return',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated list (e.g., "email,firstname,lastname")',
|
||||
condition: { field: 'operation', value: ['get_contacts', 'get_companies', 'get_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'associations',
|
||||
title: 'Associations',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated object types (e.g., "companies,deals")',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_contacts', 'get_companies', 'get_deals', 'create_contact', 'create_company'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Max results (list: 100, search: 200)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_users',
|
||||
'get_contacts',
|
||||
'get_companies',
|
||||
'get_deals',
|
||||
'search_contacts',
|
||||
'search_companies',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'after',
|
||||
title: 'After (Pagination)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Pagination cursor from previous response',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_contacts',
|
||||
'get_companies',
|
||||
'get_deals',
|
||||
'search_contacts',
|
||||
'search_companies',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Search Query',
|
||||
type: 'short-input',
|
||||
placeholder: 'Search term (e.g., company name, contact email)',
|
||||
condition: { field: 'operation', value: ['search_contacts', 'search_companies'] },
|
||||
},
|
||||
{
|
||||
id: 'filterGroups',
|
||||
title: 'Filter Groups',
|
||||
type: 'long-input',
|
||||
placeholder:
|
||||
'JSON array of filter groups (e.g., [{"filters":[{"propertyName":"email","operator":"EQ","value":"test@example.com"}]}])',
|
||||
condition: { field: 'operation', value: ['search_contacts', 'search_companies'] },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert HubSpot CRM developer. Generate HubSpot filter groups as JSON arrays based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the JSON array of filter groups. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON array that can be used directly in HubSpot API search operations.
|
||||
|
||||
### HUBSPOT FILTER GROUPS STRUCTURE
|
||||
Filter groups are arrays of filter objects. Each filter group contains an array of filters. Multiple filter groups are combined with OR logic, while filters within a group are combined with AND logic.
|
||||
|
||||
Structure:
|
||||
[
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "property_name",
|
||||
"operator": "OPERATOR",
|
||||
"value": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
### FILTER OPERATORS
|
||||
HubSpot supports the following operators:
|
||||
|
||||
**Comparison Operators**:
|
||||
- **EQ**: Equals - exact match
|
||||
- **NEQ**: Not equals
|
||||
- **LT**: Less than (for numbers and dates)
|
||||
- **LTE**: Less than or equal to
|
||||
- **GT**: Greater than (for numbers and dates)
|
||||
- **GTE**: Greater than or equal to
|
||||
- **BETWEEN**: Between two values (requires "highValue" field)
|
||||
|
||||
**String Operators**:
|
||||
- **CONTAINS_TOKEN**: Contains the token (word)
|
||||
- **NOT_CONTAINS_TOKEN**: Does not contain the token
|
||||
|
||||
**Existence Operators**:
|
||||
- **HAS_PROPERTY**: Property has any value (value can be "*")
|
||||
- **NOT_HAS_PROPERTY**: Property has no value (value can be "*")
|
||||
|
||||
**Set Operators**:
|
||||
- **IN**: Value is in the provided list (value is semicolon-separated)
|
||||
- **NOT_IN**: Value is not in the provided list
|
||||
|
||||
### COMMON CONTACT PROPERTIES FOR FILTERING
|
||||
- **email**: Email address
|
||||
- **firstname**: First name
|
||||
- **lastname**: Last name
|
||||
- **lifecyclestage**: Lifecycle stage (lead, customer, subscriber, opportunity)
|
||||
- **hs_lead_status**: Lead status (NEW, OPEN, IN_PROGRESS, QUALIFIED)
|
||||
- **createdate**: Creation date (milliseconds timestamp)
|
||||
- **lastmodifieddate**: Last modified date
|
||||
- **phone**: Phone number
|
||||
- **company**: Company name
|
||||
- **jobtitle**: Job title
|
||||
|
||||
### COMMON COMPANY PROPERTIES FOR FILTERING
|
||||
- **name**: Company name
|
||||
- **domain**: Company domain
|
||||
- **industry**: Industry
|
||||
- **type**: Company type
|
||||
- **city**: City
|
||||
- **state**: State
|
||||
- **country**: Country
|
||||
- **numberofemployees**: Number of employees
|
||||
- **annualrevenue**: Annual revenue
|
||||
- **createdate**: Creation date
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Simple Equality**: "Find contacts with email john@example.com"
|
||||
→ [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "email",
|
||||
"operator": "EQ",
|
||||
"value": "john@example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
**Multiple Filters (AND)**: "Find lead contacts in San Francisco"
|
||||
→ [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "lifecyclestage",
|
||||
"operator": "EQ",
|
||||
"value": "lead"
|
||||
},
|
||||
{
|
||||
"propertyName": "city",
|
||||
"operator": "EQ",
|
||||
"value": "San Francisco"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
**Multiple Filter Groups (OR)**: "Find contacts who are either leads or customers"
|
||||
→ [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "lifecyclestage",
|
||||
"operator": "EQ",
|
||||
"value": "lead"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "lifecyclestage",
|
||||
"operator": "EQ",
|
||||
"value": "customer"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
**Contains Text**: "Find contacts with Gmail addresses"
|
||||
→ [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "email",
|
||||
"operator": "CONTAINS_TOKEN",
|
||||
"value": "@gmail.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
**IN Operator**: "Find companies in tech or finance industries"
|
||||
→ [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "industry",
|
||||
"operator": "IN",
|
||||
"value": "TECHNOLOGY;FINANCE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
**Has Property**: "Find contacts with phone numbers"
|
||||
→ [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "phone",
|
||||
"operator": "HAS_PROPERTY",
|
||||
"value": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
**Range Filter**: "Find companies with 10 to 100 employees"
|
||||
→ [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"propertyName": "numberofemployees",
|
||||
"operator": "GTE",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"propertyName": "numberofemployees",
|
||||
"operator": "LTE",
|
||||
"value": "100"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the JSON array of filter groups - no explanations, no markdown, no extra text.`,
|
||||
placeholder: 'Describe the filters you want to apply...',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'sorts',
|
||||
title: 'Sort Order',
|
||||
type: 'long-input',
|
||||
placeholder:
|
||||
'JSON array of sort objects (e.g., [{"propertyName":"createdate","direction":"DESCENDING"}])',
|
||||
condition: { field: 'operation', value: ['search_contacts', 'search_companies'] },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert HubSpot CRM developer. Generate HubSpot sort arrays as JSON based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the JSON array of sort objects. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON array that can be used directly in HubSpot API search operations.
|
||||
|
||||
### HUBSPOT SORT STRUCTURE
|
||||
Sorts are defined as an array of objects, each containing a property name and a direction. Results will be sorted by the first sort object, then by the second if values are equal, and so on.
|
||||
|
||||
Structure:
|
||||
[
|
||||
{
|
||||
"propertyName": "property_name",
|
||||
"direction": "ASCENDING" | "DESCENDING"
|
||||
}
|
||||
]
|
||||
|
||||
### SORT DIRECTIONS
|
||||
- **ASCENDING**: Sort from lowest to highest (A-Z, 0-9, oldest to newest)
|
||||
- **DESCENDING**: Sort from highest to lowest (Z-A, 9-0, newest to oldest)
|
||||
|
||||
### COMMON SORTABLE PROPERTIES
|
||||
|
||||
**Contact Properties**:
|
||||
- **createdate**: Creation date (when the contact was created)
|
||||
- **lastmodifieddate**: Last modified date (when the contact was last updated)
|
||||
- **firstname**: First name (alphabetical)
|
||||
- **lastname**: Last name (alphabetical)
|
||||
- **email**: Email address (alphabetical)
|
||||
- **lifecyclestage**: Lifecycle stage
|
||||
- **hs_lead_status**: Lead status
|
||||
- **company**: Company name (alphabetical)
|
||||
- **jobtitle**: Job title (alphabetical)
|
||||
- **phone**: Phone number
|
||||
|
||||
**Company Properties**:
|
||||
- **createdate**: Creation date
|
||||
- **lastmodifieddate**: Last modified date
|
||||
- **name**: Company name (alphabetical)
|
||||
- **domain**: Domain (alphabetical)
|
||||
- **industry**: Industry
|
||||
- **city**: City (alphabetical)
|
||||
- **state**: State (alphabetical)
|
||||
- **numberofemployees**: Number of employees (numeric)
|
||||
- **annualrevenue**: Annual revenue (numeric)
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Simple Sort**: "Sort by creation date, newest first"
|
||||
→ [
|
||||
{
|
||||
"propertyName": "createdate",
|
||||
"direction": "DESCENDING"
|
||||
}
|
||||
]
|
||||
|
||||
**Alphabetical Sort**: "Sort contacts by last name A to Z"
|
||||
→ [
|
||||
{
|
||||
"propertyName": "lastname",
|
||||
"direction": "ASCENDING"
|
||||
}
|
||||
]
|
||||
|
||||
**Multiple Sorts**: "Sort by lifecycle stage, then by last name"
|
||||
→ [
|
||||
{
|
||||
"propertyName": "lifecyclestage",
|
||||
"direction": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"propertyName": "lastname",
|
||||
"direction": "ASCENDING"
|
||||
}
|
||||
]
|
||||
|
||||
**Numeric Sort**: "Sort companies by revenue, highest first"
|
||||
→ [
|
||||
{
|
||||
"propertyName": "annualrevenue",
|
||||
"direction": "DESCENDING"
|
||||
}
|
||||
]
|
||||
|
||||
**Recent First**: "Show most recently updated contacts first"
|
||||
→ [
|
||||
{
|
||||
"propertyName": "lastmodifieddate",
|
||||
"direction": "DESCENDING"
|
||||
}
|
||||
]
|
||||
|
||||
**Name and Date**: "Sort by company name, then by creation date newest first"
|
||||
→ [
|
||||
{
|
||||
"propertyName": "name",
|
||||
"direction": "ASCENDING"
|
||||
},
|
||||
{
|
||||
"propertyName": "createdate",
|
||||
"direction": "DESCENDING"
|
||||
}
|
||||
]
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the JSON array of sort objects - no explanations, no markdown, no extra text.`,
|
||||
placeholder: 'Describe how you want to sort the results...',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'searchProperties',
|
||||
title: 'Properties to Return',
|
||||
type: 'long-input',
|
||||
placeholder: 'JSON array of properties (e.g., ["email","firstname","lastname"])',
|
||||
condition: { field: 'operation', value: ['search_contacts', 'search_companies'] },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert HubSpot CRM developer. Generate HubSpot property arrays as JSON based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the JSON array of property names. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON array of strings that can be used directly in HubSpot API search operations.
|
||||
|
||||
### HUBSPOT PROPERTIES ARRAY STRUCTURE
|
||||
Properties to return are defined as a simple array of property name strings. These specify which fields should be included in the search results.
|
||||
|
||||
Structure:
|
||||
["property1", "property2", "property3"]
|
||||
|
||||
### COMMON CONTACT PROPERTIES
|
||||
|
||||
**Basic Information**:
|
||||
- **email**: Email address
|
||||
- **firstname**: First name
|
||||
- **lastname**: Last name
|
||||
- **phone**: Phone number
|
||||
- **mobilephone**: Mobile phone number
|
||||
|
||||
**Professional Information**:
|
||||
- **company**: Company name
|
||||
- **jobtitle**: Job title
|
||||
- **industry**: Industry
|
||||
- **department**: Department
|
||||
- **seniority**: Seniority level
|
||||
|
||||
**Address Information**:
|
||||
- **address**: Street address
|
||||
- **city**: City
|
||||
- **state**: State/Region
|
||||
- **zip**: Postal code
|
||||
- **country**: Country
|
||||
|
||||
**CRM Information**:
|
||||
- **lifecyclestage**: Lifecycle stage
|
||||
- **hs_lead_status**: Lead status
|
||||
- **hubspot_owner_id**: Owner ID
|
||||
- **hs_analytics_source**: Original source
|
||||
|
||||
**Dates**:
|
||||
- **createdate**: Creation date
|
||||
- **lastmodifieddate**: Last modified date
|
||||
- **hs_lifecyclestage_lead_date**: Lead date
|
||||
- **hs_lifecyclestage_customer_date**: Customer date
|
||||
|
||||
**Website & Social**:
|
||||
- **website**: Website URL
|
||||
- **linkedin_url**: LinkedIn profile URL
|
||||
- **twitterhandle**: Twitter handle
|
||||
|
||||
### COMMON COMPANY PROPERTIES
|
||||
|
||||
**Basic Information**:
|
||||
- **name**: Company name
|
||||
- **domain**: Company domain
|
||||
- **phone**: Phone number
|
||||
- **industry**: Industry
|
||||
- **type**: Company type
|
||||
|
||||
**Address Information**:
|
||||
- **city**: City
|
||||
- **state**: State/Region
|
||||
- **zip**: Postal code
|
||||
- **country**: Country
|
||||
- **address**: Street address
|
||||
|
||||
**Business Information**:
|
||||
- **numberofemployees**: Number of employees
|
||||
- **annualrevenue**: Annual revenue
|
||||
- **founded_year**: Year founded
|
||||
- **description**: Company description
|
||||
|
||||
**Website & Social**:
|
||||
- **website**: Website URL
|
||||
- **linkedin_company_page**: LinkedIn company page
|
||||
- **twitterhandle**: Twitter handle
|
||||
- **facebook_company_page**: Facebook page
|
||||
|
||||
**CRM Information**:
|
||||
- **hubspot_owner_id**: Owner ID
|
||||
- **createdate**: Creation date
|
||||
- **lastmodifieddate**: Last modified date
|
||||
- **hs_lastmodifieddate**: Last modified date (detailed)
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Basic Contact Fields**: "Return email, name, and phone"
|
||||
→ ["email", "firstname", "lastname", "phone"]
|
||||
|
||||
**Complete Contact Profile**: "Return all contact details"
|
||||
→ ["email", "firstname", "lastname", "phone", "mobilephone", "company", "jobtitle", "address", "city", "state", "zip", "country", "lifecyclestage", "hs_lead_status", "createdate"]
|
||||
|
||||
**Business Contact Info**: "Return professional information"
|
||||
→ ["email", "firstname", "lastname", "company", "jobtitle", "phone", "industry"]
|
||||
|
||||
**Basic Company Fields**: "Return company name, domain, and industry"
|
||||
→ ["name", "domain", "industry"]
|
||||
|
||||
**Complete Company Profile**: "Return all company information"
|
||||
→ ["name", "domain", "industry", "phone", "city", "state", "country", "numberofemployees", "annualrevenue", "website", "description", "type", "createdate"]
|
||||
|
||||
**Contact with Dates**: "Return contact info with timestamps"
|
||||
→ ["email", "firstname", "lastname", "createdate", "lastmodifieddate", "lifecyclestage"]
|
||||
|
||||
**Company Financial Info**: "Return company size and revenue"
|
||||
→ ["name", "domain", "numberofemployees", "annualrevenue", "industry"]
|
||||
|
||||
**Social Media Properties**: "Return social media links"
|
||||
→ ["email", "firstname", "lastname", "linkedin_url", "twitterhandle"]
|
||||
|
||||
**CRM Status Fields**: "Return lifecycle and owner information"
|
||||
→ ["email", "firstname", "lastname", "lifecyclestage", "hs_lead_status", "hubspot_owner_id"]
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the JSON array of property names - no explanations, no markdown, no extra text.`,
|
||||
placeholder: 'Describe which properties you want to return...',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'selectedTriggerId',
|
||||
title: 'Trigger Type',
|
||||
type: 'dropdown',
|
||||
mode: 'trigger',
|
||||
options: hubspotAllTriggerOptions,
|
||||
value: () => 'hubspot_contact_created',
|
||||
required: true,
|
||||
},
|
||||
...getTrigger('hubspot_contact_created').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_contact_deleted').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_contact_privacy_deleted').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_contact_property_changed').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_company_created').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_company_deleted').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_company_property_changed').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_conversation_creation').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_conversation_deletion').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_conversation_new_message').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_conversation_privacy_deletion').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_conversation_property_changed').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_deal_created').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_deal_deleted').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_deal_property_changed').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_ticket_created').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_ticket_deleted').subBlocks.slice(1),
|
||||
...getTrigger('hubspot_ticket_property_changed').subBlocks.slice(1),
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'hubspot_get_users',
|
||||
'hubspot_list_contacts',
|
||||
'hubspot_get_contact',
|
||||
'hubspot_create_contact',
|
||||
'hubspot_update_contact',
|
||||
'hubspot_search_contacts',
|
||||
'hubspot_list_companies',
|
||||
'hubspot_get_company',
|
||||
'hubspot_create_company',
|
||||
'hubspot_update_company',
|
||||
'hubspot_search_companies',
|
||||
'hubspot_list_deals',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'get_users':
|
||||
return 'hubspot_get_users'
|
||||
case 'get_contacts':
|
||||
return params.contactId ? 'hubspot_get_contact' : 'hubspot_list_contacts'
|
||||
case 'create_contact':
|
||||
return 'hubspot_create_contact'
|
||||
case 'update_contact':
|
||||
return 'hubspot_update_contact'
|
||||
case 'search_contacts':
|
||||
return 'hubspot_search_contacts'
|
||||
case 'get_companies':
|
||||
return params.companyId ? 'hubspot_get_company' : 'hubspot_list_companies'
|
||||
case 'create_company':
|
||||
return 'hubspot_create_company'
|
||||
case 'update_company':
|
||||
return 'hubspot_update_company'
|
||||
case 'search_companies':
|
||||
return 'hubspot_search_companies'
|
||||
case 'get_deals':
|
||||
return 'hubspot_list_deals'
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const {
|
||||
credential,
|
||||
operation,
|
||||
propertiesToSet,
|
||||
properties,
|
||||
searchProperties,
|
||||
filterGroups,
|
||||
sorts,
|
||||
associations,
|
||||
...rest
|
||||
} = params
|
||||
|
||||
const cleanParams: Record<string, any> = {
|
||||
credential,
|
||||
}
|
||||
|
||||
if (propertiesToSet) {
|
||||
try {
|
||||
cleanParams.properties =
|
||||
typeof propertiesToSet === 'string' ? JSON.parse(propertiesToSet) : propertiesToSet
|
||||
} catch (error) {
|
||||
throw new Error('Invalid JSON in properties field')
|
||||
}
|
||||
}
|
||||
|
||||
if (properties && !searchProperties) {
|
||||
cleanParams.properties = properties
|
||||
}
|
||||
|
||||
if (searchProperties) {
|
||||
try {
|
||||
cleanParams.properties =
|
||||
typeof searchProperties === 'string' ? JSON.parse(searchProperties) : searchProperties
|
||||
} catch (error) {
|
||||
throw new Error('Invalid JSON in searchProperties field')
|
||||
}
|
||||
}
|
||||
|
||||
if (filterGroups) {
|
||||
try {
|
||||
cleanParams.filterGroups =
|
||||
typeof filterGroups === 'string' ? JSON.parse(filterGroups) : filterGroups
|
||||
} catch (error) {
|
||||
throw new Error('Invalid JSON in filterGroups field')
|
||||
}
|
||||
}
|
||||
|
||||
if (sorts) {
|
||||
try {
|
||||
cleanParams.sorts = typeof sorts === 'string' ? JSON.parse(sorts) : sorts
|
||||
} catch (error) {
|
||||
throw new Error('Invalid JSON in sorts field')
|
||||
}
|
||||
}
|
||||
|
||||
if (associations) {
|
||||
cleanParams.associations = associations
|
||||
}
|
||||
|
||||
// Add other params
|
||||
Object.entries(rest).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
cleanParams[key] = value
|
||||
}
|
||||
})
|
||||
|
||||
return cleanParams
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
credential: { type: 'string', description: 'HubSpot access token' },
|
||||
contactId: { type: 'string', description: 'Contact ID or email' },
|
||||
companyId: { type: 'string', description: 'Company ID or domain' },
|
||||
idProperty: { type: 'string', description: 'Property name to use as unique identifier' },
|
||||
propertiesToSet: { type: 'json', description: 'Properties to create/update (JSON object)' },
|
||||
properties: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated properties to return (for list/get)',
|
||||
},
|
||||
associations: { type: 'string', description: 'Comma-separated object types for associations' },
|
||||
limit: { type: 'string', description: 'Maximum results (list: 100, search: 200)' },
|
||||
after: { type: 'string', description: 'Pagination cursor' },
|
||||
query: { type: 'string', description: 'Search query string' },
|
||||
filterGroups: { type: 'json', description: 'Filter groups for search (JSON array)' },
|
||||
sorts: { type: 'json', description: 'Sort order (JSON array of strings or objects)' },
|
||||
searchProperties: { type: 'json', description: 'Properties to return in search (JSON array)' },
|
||||
},
|
||||
outputs: {
|
||||
users: { type: 'json', description: 'Array of user objects' },
|
||||
contacts: { type: 'json', description: 'Array of contact objects' },
|
||||
contact: { type: 'json', description: 'Single contact object' },
|
||||
companies: { type: 'json', description: 'Array of company objects' },
|
||||
company: { type: 'json', description: 'Single company object' },
|
||||
deals: { type: 'json', description: 'Array of deal objects' },
|
||||
total: { type: 'number', description: 'Total number of matching results (for search)' },
|
||||
paging: { type: 'json', description: 'Pagination info with next/prev cursors' },
|
||||
metadata: { type: 'json', description: 'Operation metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
payload: {
|
||||
type: 'json',
|
||||
description: 'Full webhook payload array from HubSpot containing event details',
|
||||
},
|
||||
provider: {
|
||||
type: 'string',
|
||||
description: 'Provider name (hubspot)',
|
||||
},
|
||||
providerConfig: {
|
||||
appId: {
|
||||
type: 'string',
|
||||
description: 'HubSpot App ID',
|
||||
},
|
||||
clientId: {
|
||||
type: 'string',
|
||||
description: 'HubSpot Client ID',
|
||||
},
|
||||
triggerId: {
|
||||
type: 'string',
|
||||
description: 'Trigger ID (e.g., hubspot_company_created)',
|
||||
},
|
||||
clientSecret: {
|
||||
type: 'string',
|
||||
description: 'HubSpot Client Secret',
|
||||
},
|
||||
developerApiKey: {
|
||||
type: 'string',
|
||||
description: 'HubSpot Developer API Key',
|
||||
},
|
||||
curlSetWebhookUrl: {
|
||||
type: 'string',
|
||||
description: 'curl command to set webhook URL',
|
||||
},
|
||||
curlCreateSubscription: {
|
||||
type: 'string',
|
||||
description: 'curl command to create subscription',
|
||||
},
|
||||
webhookUrlDisplay: {
|
||||
type: 'string',
|
||||
description: 'Webhook URL display value',
|
||||
},
|
||||
propertyName: {
|
||||
type: 'string',
|
||||
description: 'Optional property name filter (for property change triggers)',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
triggerAllowed: true,
|
||||
triggers: {
|
||||
enabled: true,
|
||||
available: [
|
||||
'hubspot_contact_created',
|
||||
'hubspot_contact_deleted',
|
||||
'hubspot_contact_privacy_deleted',
|
||||
'hubspot_contact_property_changed',
|
||||
'hubspot_company_created',
|
||||
'hubspot_company_deleted',
|
||||
'hubspot_company_property_changed',
|
||||
'hubspot_conversation_creation',
|
||||
'hubspot_conversation_deletion',
|
||||
'hubspot_conversation_new_message',
|
||||
'hubspot_conversation_privacy_deletion',
|
||||
'hubspot_conversation_property_changed',
|
||||
'hubspot_deal_created',
|
||||
'hubspot_deal_deleted',
|
||||
'hubspot_deal_property_changed',
|
||||
'hubspot_ticket_created',
|
||||
'hubspot_ticket_deleted',
|
||||
'hubspot_ticket_property_changed',
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -32,7 +32,14 @@ export const MicrosoftExcelBlock: BlockConfig<MicrosoftExcelResponse> = {
|
||||
type: 'oauth-input',
|
||||
provider: 'microsoft-excel',
|
||||
serviceId: 'microsoft-excel',
|
||||
requiredScopes: [],
|
||||
requiredScopes: [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'Files.Read',
|
||||
'Files.ReadWrite',
|
||||
'offline_access',
|
||||
],
|
||||
placeholder: 'Select Microsoft account',
|
||||
required: true,
|
||||
},
|
||||
|
||||
733
apps/sim/blocks/blocks/pipedrive.ts
Normal file
733
apps/sim/blocks/blocks/pipedrive.ts
Normal file
@@ -0,0 +1,733 @@
|
||||
import { PipedriveIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { PipedriveResponse } from '@/tools/pipedrive/types'
|
||||
|
||||
export const PipedriveBlock: BlockConfig<PipedriveResponse> = {
|
||||
type: 'pipedrive',
|
||||
name: 'Pipedrive',
|
||||
description: 'Interact with Pipedrive CRM',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate Pipedrive into your workflow. Manage deals, contacts, sales pipeline, projects, activities, files, and communications with powerful CRM capabilities.',
|
||||
docsLink: 'https://docs.sim.ai/tools/pipedrive',
|
||||
category: 'tools',
|
||||
bgColor: '#2E6936',
|
||||
icon: PipedriveIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Get All Deals', id: 'get_all_deals' },
|
||||
{ label: 'Get Deal', id: 'get_deal' },
|
||||
{ label: 'Create Deal', id: 'create_deal' },
|
||||
{ label: 'Update Deal', id: 'update_deal' },
|
||||
{ label: 'Get Files', id: 'get_files' },
|
||||
{ label: 'Get Mail Threads', id: 'get_mail_messages' },
|
||||
{ label: 'Get Mail Thread Messages', id: 'get_mail_thread' },
|
||||
{ label: 'Get Pipelines', id: 'get_pipelines' },
|
||||
{ label: 'Get Pipeline Deals', id: 'get_pipeline_deals' },
|
||||
{ label: 'Get Projects', id: 'get_projects' },
|
||||
{ label: 'Create Project', id: 'create_project' },
|
||||
{ label: 'Get Activities', id: 'get_activities' },
|
||||
{ label: 'Create Activity', id: 'create_activity' },
|
||||
{ label: 'Update Activity', id: 'update_activity' },
|
||||
{ label: 'Get Leads', id: 'get_leads' },
|
||||
{ label: 'Create Lead', id: 'create_lead' },
|
||||
{ label: 'Update Lead', id: 'update_lead' },
|
||||
{ label: 'Delete Lead', id: 'delete_lead' },
|
||||
],
|
||||
value: () => 'get_all_deals',
|
||||
},
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Pipedrive Account',
|
||||
type: 'oauth-input',
|
||||
provider: 'pipedrive',
|
||||
serviceId: 'pipedrive',
|
||||
requiredScopes: [
|
||||
'base',
|
||||
'deals:full',
|
||||
'contacts:full',
|
||||
'leads:full',
|
||||
'activities:full',
|
||||
'mail:full',
|
||||
'projects:full',
|
||||
],
|
||||
placeholder: 'Select Pipedrive account',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All (not deleted)', id: '' },
|
||||
{ label: 'Open', id: 'open' },
|
||||
{ label: 'Won', id: 'won' },
|
||||
{ label: 'Lost', id: 'lost' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: ['get_all_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'person_id',
|
||||
title: 'Person ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by person ID',
|
||||
condition: { field: 'operation', value: ['get_all_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'org_id',
|
||||
title: 'Organization ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by organization ID',
|
||||
condition: { field: 'operation', value: ['get_all_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'pipeline_id',
|
||||
title: 'Pipeline ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by pipeline ID ',
|
||||
condition: { field: 'operation', value: ['get_all_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'updated_since',
|
||||
title: 'Updated Since',
|
||||
type: 'short-input',
|
||||
placeholder: 'Date (2025-01-01T10:20:00Z)',
|
||||
condition: { field: 'operation', value: ['get_all_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 100, max 500)',
|
||||
condition: { field: 'operation', value: ['get_all_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'deal_id',
|
||||
title: 'Deal ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter deal ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_deal', 'update_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
title: 'Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter deal title',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
title: 'Value',
|
||||
type: 'short-input',
|
||||
placeholder: 'Monetary value ',
|
||||
condition: { field: 'operation', value: ['create_deal', 'update_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'currency',
|
||||
title: 'Currency',
|
||||
type: 'short-input',
|
||||
placeholder: 'Currency code (e.g., USD, EUR)',
|
||||
condition: { field: 'operation', value: ['create_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'person_id',
|
||||
title: 'Person ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Associated person ID ',
|
||||
condition: { field: 'operation', value: ['create_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'org_id',
|
||||
title: 'Organization ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Associated organization ID ',
|
||||
condition: { field: 'operation', value: ['create_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'pipeline_id',
|
||||
title: 'Pipeline ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Pipeline ID ',
|
||||
condition: { field: 'operation', value: ['create_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'stage_id',
|
||||
title: 'Stage ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Stage ID ',
|
||||
condition: { field: 'operation', value: ['create_deal', 'update_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Open', id: 'open' },
|
||||
{ label: 'Won', id: 'won' },
|
||||
{ label: 'Lost', id: 'lost' },
|
||||
],
|
||||
value: () => 'open',
|
||||
condition: { field: 'operation', value: ['create_deal', 'update_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'expected_close_date',
|
||||
title: 'Expected Close Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD ',
|
||||
condition: { field: 'operation', value: ['create_deal', 'update_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
title: 'New Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'New deal title ',
|
||||
condition: { field: 'operation', value: ['update_deal'] },
|
||||
},
|
||||
{
|
||||
id: 'deal_id',
|
||||
title: 'Deal ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by deal ID ',
|
||||
condition: { field: 'operation', value: ['get_files'] },
|
||||
},
|
||||
{
|
||||
id: 'person_id',
|
||||
title: 'Person ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by person ID ',
|
||||
condition: { field: 'operation', value: ['get_files'] },
|
||||
},
|
||||
{
|
||||
id: 'org_id',
|
||||
title: 'Organization ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by organization ID ',
|
||||
condition: { field: 'operation', value: ['get_files'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 100, max 500)',
|
||||
condition: { field: 'operation', value: ['get_files'] },
|
||||
},
|
||||
{
|
||||
id: 'folder',
|
||||
title: 'Folder',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Inbox', id: 'inbox' },
|
||||
{ label: 'Drafts', id: 'drafts' },
|
||||
{ label: 'Sent', id: 'sent' },
|
||||
{ label: 'Archive', id: 'archive' },
|
||||
],
|
||||
value: () => 'inbox',
|
||||
condition: { field: 'operation', value: ['get_mail_messages'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 50)',
|
||||
condition: { field: 'operation', value: ['get_mail_messages'] },
|
||||
},
|
||||
{
|
||||
id: 'thread_id',
|
||||
title: 'Thread ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter mail thread ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_mail_thread'] },
|
||||
},
|
||||
{
|
||||
id: 'sort_by',
|
||||
title: 'Sort By',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'ID', id: 'id' },
|
||||
{ label: 'Update Time', id: 'update_time' },
|
||||
{ label: 'Add Time', id: 'add_time' },
|
||||
],
|
||||
value: () => 'id',
|
||||
condition: { field: 'operation', value: ['get_pipelines'] },
|
||||
},
|
||||
{
|
||||
id: 'sort_direction',
|
||||
title: 'Sort Direction',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Ascending', id: 'asc' },
|
||||
{ label: 'Descending', id: 'desc' },
|
||||
],
|
||||
value: () => 'asc',
|
||||
condition: { field: 'operation', value: ['get_pipelines'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 100, max 500)',
|
||||
condition: { field: 'operation', value: ['get_pipelines'] },
|
||||
},
|
||||
{
|
||||
id: 'cursor',
|
||||
title: 'Cursor',
|
||||
type: 'short-input',
|
||||
placeholder: 'Pagination cursor (optional)',
|
||||
condition: { field: 'operation', value: ['get_pipelines'] },
|
||||
},
|
||||
{
|
||||
id: 'pipeline_id',
|
||||
title: 'Pipeline ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter pipeline ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_pipeline_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'stage_id',
|
||||
title: 'Stage ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by stage ID ',
|
||||
condition: { field: 'operation', value: ['get_pipeline_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: 'Open', id: 'open' },
|
||||
{ label: 'Won', id: 'won' },
|
||||
{ label: 'Lost', id: 'lost' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: ['get_pipeline_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 100, max 500)',
|
||||
condition: { field: 'operation', value: ['get_pipeline_deals'] },
|
||||
},
|
||||
{
|
||||
id: 'project_id',
|
||||
title: 'Project ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Project ID',
|
||||
condition: { field: 'operation', value: ['get_projects'] },
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: 'Open', id: 'open' },
|
||||
{ label: 'Completed', id: 'completed' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: ['get_projects'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 100, max 500)',
|
||||
condition: { field: 'operation', value: ['get_projects'] },
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
title: 'Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter project title',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_project'] },
|
||||
},
|
||||
{
|
||||
id: 'description',
|
||||
title: 'Description',
|
||||
type: 'long-input',
|
||||
placeholder: 'Project description ',
|
||||
condition: { field: 'operation', value: ['create_project'] },
|
||||
},
|
||||
{
|
||||
id: 'start_date',
|
||||
title: 'Start Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD ',
|
||||
condition: { field: 'operation', value: ['create_project'] },
|
||||
},
|
||||
{
|
||||
id: 'end_date',
|
||||
title: 'End Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD ',
|
||||
condition: { field: 'operation', value: ['create_project'] },
|
||||
},
|
||||
{
|
||||
id: 'deal_id',
|
||||
title: 'Deal ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by deal ID ',
|
||||
condition: { field: 'operation', value: ['get_activities', 'create_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'person_id',
|
||||
title: 'Person ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by person ID ',
|
||||
condition: { field: 'operation', value: ['get_activities', 'create_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'org_id',
|
||||
title: 'Organization ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by organization ID ',
|
||||
condition: { field: 'operation', value: ['get_activities', 'create_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
title: 'Activity Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: 'Call', id: 'call' },
|
||||
{ label: 'Meeting', id: 'meeting' },
|
||||
{ label: 'Task', id: 'task' },
|
||||
{ label: 'Deadline', id: 'deadline' },
|
||||
{ label: 'Email', id: 'email' },
|
||||
{ label: 'Lunch', id: 'lunch' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: ['get_activities'] },
|
||||
},
|
||||
{
|
||||
id: 'done',
|
||||
title: 'Completion Status',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: 'Not Done', id: '0' },
|
||||
{ label: 'Done', id: '1' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: ['get_activities'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 100, max 500)',
|
||||
condition: { field: 'operation', value: ['get_activities'] },
|
||||
},
|
||||
{
|
||||
id: 'subject',
|
||||
title: 'Subject',
|
||||
type: 'short-input',
|
||||
placeholder: 'Activity subject/title',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_activity', 'update_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
title: 'Activity Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Call', id: 'call' },
|
||||
{ label: 'Meeting', id: 'meeting' },
|
||||
{ label: 'Task', id: 'task' },
|
||||
{ label: 'Deadline', id: 'deadline' },
|
||||
{ label: 'Email', id: 'email' },
|
||||
{ label: 'Lunch', id: 'lunch' },
|
||||
],
|
||||
value: () => 'task',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'due_date',
|
||||
title: 'Due Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_activity', 'update_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'due_time',
|
||||
title: 'Due Time',
|
||||
type: 'short-input',
|
||||
placeholder: 'HH:MM ',
|
||||
condition: { field: 'operation', value: ['create_activity', 'update_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
title: 'Duration',
|
||||
type: 'short-input',
|
||||
placeholder: 'HH:MM ',
|
||||
condition: { field: 'operation', value: ['create_activity', 'update_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'note',
|
||||
title: 'Notes',
|
||||
type: 'long-input',
|
||||
placeholder: 'Activity notes ',
|
||||
condition: { field: 'operation', value: ['create_activity', 'update_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'activity_id',
|
||||
title: 'Activity ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter activity ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['update_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'done',
|
||||
title: 'Mark as Done',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Not Done', id: '0' },
|
||||
{ label: 'Done', id: '1' },
|
||||
],
|
||||
value: () => '0',
|
||||
condition: { field: 'operation', value: ['update_activity'] },
|
||||
},
|
||||
{
|
||||
id: 'lead_id',
|
||||
title: 'Lead ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Lead ID',
|
||||
condition: { field: 'operation', value: ['get_leads', 'update_lead', 'delete_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'archived',
|
||||
title: 'Archived',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Active Leads', id: 'false' },
|
||||
{ label: 'Archived Leads', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: ['get_leads'] },
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
title: 'Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter lead title',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
title: 'New Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'New lead title',
|
||||
condition: { field: 'operation', value: ['update_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'person_id',
|
||||
title: 'Person ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Person ID to link lead to',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead', 'get_leads'] },
|
||||
},
|
||||
{
|
||||
id: 'organization_id',
|
||||
title: 'Organization ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Organization ID to link lead to',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead', 'get_leads'] },
|
||||
},
|
||||
{
|
||||
id: 'owner_id',
|
||||
title: 'Owner ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Owner user ID',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead', 'get_leads'] },
|
||||
},
|
||||
{
|
||||
id: 'value_amount',
|
||||
title: 'Value Amount',
|
||||
type: 'short-input',
|
||||
placeholder: 'Potential value amount',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'value_currency',
|
||||
title: 'Value Currency',
|
||||
type: 'short-input',
|
||||
placeholder: 'Currency code (e.g., USD, EUR)',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'expected_close_date',
|
||||
title: 'Expected Close Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'is_archived',
|
||||
title: 'Archive Lead',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: ['update_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results (default 100)',
|
||||
condition: { field: 'operation', value: ['get_leads'] },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'pipedrive_get_all_deals',
|
||||
'pipedrive_get_deal',
|
||||
'pipedrive_create_deal',
|
||||
'pipedrive_update_deal',
|
||||
'pipedrive_get_files',
|
||||
'pipedrive_get_mail_messages',
|
||||
'pipedrive_get_mail_thread',
|
||||
'pipedrive_get_pipelines',
|
||||
'pipedrive_get_pipeline_deals',
|
||||
'pipedrive_get_projects',
|
||||
'pipedrive_create_project',
|
||||
'pipedrive_get_activities',
|
||||
'pipedrive_create_activity',
|
||||
'pipedrive_update_activity',
|
||||
'pipedrive_get_leads',
|
||||
'pipedrive_create_lead',
|
||||
'pipedrive_update_lead',
|
||||
'pipedrive_delete_lead',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'get_all_deals':
|
||||
return 'pipedrive_get_all_deals'
|
||||
case 'get_deal':
|
||||
return 'pipedrive_get_deal'
|
||||
case 'create_deal':
|
||||
return 'pipedrive_create_deal'
|
||||
case 'update_deal':
|
||||
return 'pipedrive_update_deal'
|
||||
case 'get_files':
|
||||
return 'pipedrive_get_files'
|
||||
case 'get_mail_messages':
|
||||
return 'pipedrive_get_mail_messages'
|
||||
case 'get_mail_thread':
|
||||
return 'pipedrive_get_mail_thread'
|
||||
case 'get_pipelines':
|
||||
return 'pipedrive_get_pipelines'
|
||||
case 'get_pipeline_deals':
|
||||
return 'pipedrive_get_pipeline_deals'
|
||||
case 'get_projects':
|
||||
return 'pipedrive_get_projects'
|
||||
case 'create_project':
|
||||
return 'pipedrive_create_project'
|
||||
case 'get_activities':
|
||||
return 'pipedrive_get_activities'
|
||||
case 'create_activity':
|
||||
return 'pipedrive_create_activity'
|
||||
case 'update_activity':
|
||||
return 'pipedrive_update_activity'
|
||||
case 'get_leads':
|
||||
return 'pipedrive_get_leads'
|
||||
case 'create_lead':
|
||||
return 'pipedrive_create_lead'
|
||||
case 'update_lead':
|
||||
return 'pipedrive_update_lead'
|
||||
case 'delete_lead':
|
||||
return 'pipedrive_delete_lead'
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { credential, operation, ...rest } = params
|
||||
|
||||
const cleanParams: Record<string, any> = {
|
||||
credential,
|
||||
}
|
||||
|
||||
Object.entries(rest).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
cleanParams[key] = value
|
||||
}
|
||||
})
|
||||
|
||||
return cleanParams
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
credential: { type: 'string', description: 'Pipedrive access token' },
|
||||
deal_id: { type: 'string', description: 'Deal ID' },
|
||||
title: { type: 'string', description: 'Title' },
|
||||
value: { type: 'string', description: 'Monetary value' },
|
||||
currency: { type: 'string', description: 'Currency code' },
|
||||
person_id: { type: 'string', description: 'Person ID' },
|
||||
org_id: { type: 'string', description: 'Organization ID' },
|
||||
pipeline_id: { type: 'string', description: 'Pipeline ID' },
|
||||
stage_id: { type: 'string', description: 'Stage ID' },
|
||||
status: { type: 'string', description: 'Status' },
|
||||
expected_close_date: { type: 'string', description: 'Expected close date' },
|
||||
updated_since: { type: 'string', description: 'Updated since timestamp' },
|
||||
limit: { type: 'string', description: 'Result limit' },
|
||||
folder: { type: 'string', description: 'Mail folder' },
|
||||
thread_id: { type: 'string', description: 'Mail thread ID' },
|
||||
sort_by: { type: 'string', description: 'Field to sort by' },
|
||||
sort_direction: { type: 'string', description: 'Sorting direction' },
|
||||
cursor: { type: 'string', description: 'Pagination cursor' },
|
||||
project_id: { type: 'string', description: 'Project ID' },
|
||||
description: { type: 'string', description: 'Description' },
|
||||
start_date: { type: 'string', description: 'Start date' },
|
||||
end_date: { type: 'string', description: 'End date' },
|
||||
activity_id: { type: 'string', description: 'Activity ID' },
|
||||
subject: { type: 'string', description: 'Activity subject' },
|
||||
type: { type: 'string', description: 'Activity type' },
|
||||
due_date: { type: 'string', description: 'Due date' },
|
||||
due_time: { type: 'string', description: 'Due time' },
|
||||
duration: { type: 'string', description: 'Duration' },
|
||||
done: { type: 'string', description: 'Completion status' },
|
||||
note: { type: 'string', description: 'Notes' },
|
||||
lead_id: { type: 'string', description: 'Lead ID' },
|
||||
archived: { type: 'string', description: 'Archived status' },
|
||||
value_amount: { type: 'string', description: 'Value amount' },
|
||||
value_currency: { type: 'string', description: 'Value currency' },
|
||||
is_archived: { type: 'string', description: 'Archive status' },
|
||||
},
|
||||
outputs: {
|
||||
deals: { type: 'json', description: 'Array of deal objects' },
|
||||
deal: { type: 'json', description: 'Single deal object' },
|
||||
files: { type: 'json', description: 'Array of file objects' },
|
||||
messages: { type: 'json', description: 'Array of mail message objects' },
|
||||
pipelines: { type: 'json', description: 'Array of pipeline objects' },
|
||||
projects: { type: 'json', description: 'Array of project objects' },
|
||||
project: { type: 'json', description: 'Single project object' },
|
||||
activities: { type: 'json', description: 'Array of activity objects' },
|
||||
activity: { type: 'json', description: 'Single activity object' },
|
||||
leads: { type: 'json', description: 'Array of lead objects' },
|
||||
lead: { type: 'json', description: 'Single lead object' },
|
||||
metadata: { type: 'json', description: 'Operation metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
498
apps/sim/blocks/blocks/salesforce.ts
Normal file
498
apps/sim/blocks/blocks/salesforce.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
import { SalesforceIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { SalesforceResponse } from '@/tools/salesforce/types'
|
||||
|
||||
export const SalesforceBlock: BlockConfig<SalesforceResponse> = {
|
||||
type: 'salesforce',
|
||||
name: 'Salesforce',
|
||||
description: 'Interact with Salesforce CRM',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate Salesforce into your workflow. Manage accounts, contacts, leads, opportunities, cases, and tasks with powerful automation capabilities.',
|
||||
docsLink: 'https://docs.sim.ai/tools/salesforce',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: SalesforceIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Get Accounts', id: 'get_accounts' },
|
||||
{ label: 'Create Account', id: 'create_account' },
|
||||
{ label: 'Update Account', id: 'update_account' },
|
||||
{ label: 'Delete Account', id: 'delete_account' },
|
||||
{ label: 'Get Contacts', id: 'get_contacts' },
|
||||
{ label: 'Create Contact', id: 'create_contact' },
|
||||
{ label: 'Update Contact', id: 'update_contact' },
|
||||
{ label: 'Delete Contact', id: 'delete_contact' },
|
||||
{ label: 'Get Leads', id: 'get_leads' },
|
||||
{ label: 'Create Lead', id: 'create_lead' },
|
||||
{ label: 'Update Lead', id: 'update_lead' },
|
||||
{ label: 'Delete Lead', id: 'delete_lead' },
|
||||
{ label: 'Get Opportunities', id: 'get_opportunities' },
|
||||
{ label: 'Create Opportunity', id: 'create_opportunity' },
|
||||
{ label: 'Update Opportunity', id: 'update_opportunity' },
|
||||
{ label: 'Delete Opportunity', id: 'delete_opportunity' },
|
||||
{ label: 'Get Cases', id: 'get_cases' },
|
||||
{ label: 'Create Case', id: 'create_case' },
|
||||
{ label: 'Update Case', id: 'update_case' },
|
||||
{ label: 'Delete Case', id: 'delete_case' },
|
||||
{ label: 'Get Tasks', id: 'get_tasks' },
|
||||
{ label: 'Create Task', id: 'create_task' },
|
||||
{ label: 'Update Task', id: 'update_task' },
|
||||
{ label: 'Delete Task', id: 'delete_task' },
|
||||
],
|
||||
value: () => 'get_accounts',
|
||||
},
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Salesforce Account',
|
||||
type: 'oauth-input',
|
||||
provider: 'salesforce',
|
||||
serviceId: 'salesforce',
|
||||
requiredScopes: ['api', 'refresh_token', 'openid'],
|
||||
placeholder: 'Select Salesforce account',
|
||||
required: true,
|
||||
},
|
||||
// Common fields for GET operations
|
||||
{
|
||||
id: 'fields',
|
||||
title: 'Fields to Return',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated fields',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_accounts',
|
||||
'get_contacts',
|
||||
'get_leads',
|
||||
'get_opportunities',
|
||||
'get_cases',
|
||||
'get_tasks',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Max results (default: 100)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_accounts',
|
||||
'get_contacts',
|
||||
'get_leads',
|
||||
'get_opportunities',
|
||||
'get_cases',
|
||||
'get_tasks',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'orderBy',
|
||||
title: 'Order By',
|
||||
type: 'short-input',
|
||||
placeholder: 'Field and direction (e.g., "Name ASC")',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_accounts',
|
||||
'get_contacts',
|
||||
'get_leads',
|
||||
'get_opportunities',
|
||||
'get_cases',
|
||||
'get_tasks',
|
||||
],
|
||||
},
|
||||
},
|
||||
// Account fields
|
||||
{
|
||||
id: 'accountId',
|
||||
title: 'Account ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Salesforce Account ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'update_account',
|
||||
'delete_account',
|
||||
'create_contact',
|
||||
'update_contact',
|
||||
'create_case',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
title: 'Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Name',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_account', 'update_account', 'create_opportunity', 'update_opportunity'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
title: 'Type',
|
||||
type: 'short-input',
|
||||
placeholder: 'Type',
|
||||
condition: { field: 'operation', value: ['create_account', 'update_account'] },
|
||||
},
|
||||
{
|
||||
id: 'industry',
|
||||
title: 'Industry',
|
||||
type: 'short-input',
|
||||
placeholder: 'Industry',
|
||||
condition: { field: 'operation', value: ['create_account', 'update_account'] },
|
||||
},
|
||||
{
|
||||
id: 'phone',
|
||||
title: 'Phone',
|
||||
type: 'short-input',
|
||||
placeholder: 'Phone',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'create_account',
|
||||
'update_account',
|
||||
'create_contact',
|
||||
'update_contact',
|
||||
'create_lead',
|
||||
'update_lead',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'website',
|
||||
title: 'Website',
|
||||
type: 'short-input',
|
||||
placeholder: 'Website',
|
||||
condition: { field: 'operation', value: ['create_account', 'update_account'] },
|
||||
},
|
||||
// Contact fields
|
||||
{
|
||||
id: 'contactId',
|
||||
title: 'Contact ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Contact ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_contacts', 'update_contact', 'delete_contact', 'create_case'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lastName',
|
||||
title: 'Last Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Last name',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_contact', 'update_contact', 'create_lead', 'update_lead'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'firstName',
|
||||
title: 'First Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'First name',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_contact', 'update_contact', 'create_lead', 'update_lead'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
title: 'Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'Email',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_contact', 'update_contact', 'create_lead', 'update_lead'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
title: 'Job Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Job title',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_contact', 'update_contact', 'create_lead', 'update_lead'],
|
||||
},
|
||||
},
|
||||
// Lead fields
|
||||
{
|
||||
id: 'leadId',
|
||||
title: 'Lead ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Lead ID',
|
||||
condition: { field: 'operation', value: ['get_leads', 'update_lead', 'delete_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'company',
|
||||
title: 'Company',
|
||||
type: 'short-input',
|
||||
placeholder: 'Company name',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead'] },
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'short-input',
|
||||
placeholder: 'Status',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'create_lead',
|
||||
'update_lead',
|
||||
'create_case',
|
||||
'update_case',
|
||||
'create_task',
|
||||
'update_task',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'leadSource',
|
||||
title: 'Lead Source',
|
||||
type: 'short-input',
|
||||
placeholder: 'Lead source',
|
||||
condition: { field: 'operation', value: ['create_lead', 'update_lead'] },
|
||||
},
|
||||
// Opportunity fields
|
||||
{
|
||||
id: 'opportunityId',
|
||||
title: 'Opportunity ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Opportunity ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_opportunities', 'update_opportunity', 'delete_opportunity'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'stageName',
|
||||
title: 'Stage Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Stage name',
|
||||
condition: { field: 'operation', value: ['create_opportunity', 'update_opportunity'] },
|
||||
},
|
||||
{
|
||||
id: 'closeDate',
|
||||
title: 'Close Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (required for create)',
|
||||
condition: { field: 'operation', value: ['create_opportunity', 'update_opportunity'] },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
title: 'Amount',
|
||||
type: 'short-input',
|
||||
placeholder: 'Deal amount',
|
||||
condition: { field: 'operation', value: ['create_opportunity', 'update_opportunity'] },
|
||||
},
|
||||
{
|
||||
id: 'probability',
|
||||
title: 'Probability',
|
||||
type: 'short-input',
|
||||
placeholder: 'Win probability (0-100)',
|
||||
condition: { field: 'operation', value: ['create_opportunity', 'update_opportunity'] },
|
||||
},
|
||||
// Case fields
|
||||
{
|
||||
id: 'caseId',
|
||||
title: 'Case ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Case ID',
|
||||
condition: { field: 'operation', value: ['get_cases', 'update_case', 'delete_case'] },
|
||||
},
|
||||
{
|
||||
id: 'subject',
|
||||
title: 'Subject',
|
||||
type: 'short-input',
|
||||
placeholder: 'Subject',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_case', 'update_case', 'create_task', 'update_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'priority',
|
||||
title: 'Priority',
|
||||
type: 'short-input',
|
||||
placeholder: 'Priority',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_case', 'update_case', 'create_task', 'update_task'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'origin',
|
||||
title: 'Origin',
|
||||
type: 'short-input',
|
||||
placeholder: 'Origin (e.g., Phone, Email, Web)',
|
||||
condition: { field: 'operation', value: ['create_case'] },
|
||||
},
|
||||
// Task fields
|
||||
{
|
||||
id: 'taskId',
|
||||
title: 'Task ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Task ID',
|
||||
condition: { field: 'operation', value: ['get_tasks', 'update_task', 'delete_task'] },
|
||||
},
|
||||
{
|
||||
id: 'activityDate',
|
||||
title: 'Due Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
condition: { field: 'operation', value: ['create_task', 'update_task'] },
|
||||
},
|
||||
{
|
||||
id: 'whoId',
|
||||
title: 'Related Contact/Lead ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Contact or Lead ID',
|
||||
condition: { field: 'operation', value: ['create_task'] },
|
||||
},
|
||||
{
|
||||
id: 'whatId',
|
||||
title: 'Related Account/Opportunity ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Account or Opportunity ID',
|
||||
condition: { field: 'operation', value: ['create_task'] },
|
||||
},
|
||||
// Long-input fields at the bottom
|
||||
{
|
||||
id: 'description',
|
||||
title: 'Description',
|
||||
type: 'long-input',
|
||||
placeholder: 'Description',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'create_account',
|
||||
'update_account',
|
||||
'create_contact',
|
||||
'update_contact',
|
||||
'create_lead',
|
||||
'update_lead',
|
||||
'create_opportunity',
|
||||
'update_opportunity',
|
||||
'create_case',
|
||||
'update_case',
|
||||
'create_task',
|
||||
'update_task',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'salesforce_get_accounts',
|
||||
'salesforce_create_account',
|
||||
'salesforce_update_account',
|
||||
'salesforce_delete_account',
|
||||
'salesforce_get_contacts',
|
||||
'salesforce_create_contact',
|
||||
'salesforce_update_contact',
|
||||
'salesforce_delete_contact',
|
||||
'salesforce_get_leads',
|
||||
'salesforce_create_lead',
|
||||
'salesforce_update_lead',
|
||||
'salesforce_delete_lead',
|
||||
'salesforce_get_opportunities',
|
||||
'salesforce_create_opportunity',
|
||||
'salesforce_update_opportunity',
|
||||
'salesforce_delete_opportunity',
|
||||
'salesforce_get_cases',
|
||||
'salesforce_create_case',
|
||||
'salesforce_update_case',
|
||||
'salesforce_delete_case',
|
||||
'salesforce_get_tasks',
|
||||
'salesforce_create_task',
|
||||
'salesforce_update_task',
|
||||
'salesforce_delete_task',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'get_accounts':
|
||||
return 'salesforce_get_accounts'
|
||||
case 'create_account':
|
||||
return 'salesforce_create_account'
|
||||
case 'update_account':
|
||||
return 'salesforce_update_account'
|
||||
case 'delete_account':
|
||||
return 'salesforce_delete_account'
|
||||
case 'get_contacts':
|
||||
return 'salesforce_get_contacts'
|
||||
case 'create_contact':
|
||||
return 'salesforce_create_contact'
|
||||
case 'update_contact':
|
||||
return 'salesforce_update_contact'
|
||||
case 'delete_contact':
|
||||
return 'salesforce_delete_contact'
|
||||
case 'get_leads':
|
||||
return 'salesforce_get_leads'
|
||||
case 'create_lead':
|
||||
return 'salesforce_create_lead'
|
||||
case 'update_lead':
|
||||
return 'salesforce_update_lead'
|
||||
case 'delete_lead':
|
||||
return 'salesforce_delete_lead'
|
||||
case 'get_opportunities':
|
||||
return 'salesforce_get_opportunities'
|
||||
case 'create_opportunity':
|
||||
return 'salesforce_create_opportunity'
|
||||
case 'update_opportunity':
|
||||
return 'salesforce_update_opportunity'
|
||||
case 'delete_opportunity':
|
||||
return 'salesforce_delete_opportunity'
|
||||
case 'get_cases':
|
||||
return 'salesforce_get_cases'
|
||||
case 'create_case':
|
||||
return 'salesforce_create_case'
|
||||
case 'update_case':
|
||||
return 'salesforce_update_case'
|
||||
case 'delete_case':
|
||||
return 'salesforce_delete_case'
|
||||
case 'get_tasks':
|
||||
return 'salesforce_get_tasks'
|
||||
case 'create_task':
|
||||
return 'salesforce_create_task'
|
||||
case 'update_task':
|
||||
return 'salesforce_update_task'
|
||||
case 'delete_task':
|
||||
return 'salesforce_delete_task'
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { credential, operation, ...rest } = params
|
||||
const cleanParams: Record<string, any> = { credential }
|
||||
Object.entries(rest).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
cleanParams[key] = value
|
||||
}
|
||||
})
|
||||
return cleanParams
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
credential: { type: 'string', description: 'Salesforce credential' },
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: { type: 'json', description: 'Operation result data' },
|
||||
},
|
||||
}
|
||||
@@ -43,10 +43,9 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'Files.Read',
|
||||
'Files.ReadWrite',
|
||||
'Sites.Read.All',
|
||||
'Sites.ReadWrite.All',
|
||||
'Sites.Manage.All',
|
||||
'offline_access',
|
||||
],
|
||||
placeholder: 'Select Microsoft account',
|
||||
|
||||
@@ -59,7 +59,9 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
'chat:write.public',
|
||||
'users:read',
|
||||
'files:write',
|
||||
'files:read',
|
||||
'canvases:write',
|
||||
'reactions:write',
|
||||
],
|
||||
placeholder: 'Select Slack workspace',
|
||||
condition: {
|
||||
|
||||
415
apps/sim/blocks/blocks/trello.ts
Normal file
415
apps/sim/blocks/blocks/trello.ts
Normal file
@@ -0,0 +1,415 @@
|
||||
import { TrelloIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Trello Block
|
||||
*
|
||||
* Note: Trello uses OAuth 1.0a authentication with a unique credential ID format
|
||||
* (non-UUID strings like CUID2). This is different from most OAuth 2.0 providers
|
||||
* that use UUID-based credential IDs. The OAuth credentials API has been updated
|
||||
* to accept both UUID and non-UUID credential ID formats to support Trello.
|
||||
*/
|
||||
export const TrelloBlock: BlockConfig<ToolResponse> = {
|
||||
type: 'trello',
|
||||
name: 'Trello',
|
||||
description: 'Manage Trello boards and cards',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate with Trello to manage boards and cards. List boards, list cards, create cards, update cards, get actions, and add comments.',
|
||||
docsLink: 'https://docs.sim.ai/tools/trello',
|
||||
category: 'tools',
|
||||
bgColor: '#0052CC',
|
||||
icon: TrelloIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Get Lists', id: 'trello_list_lists' },
|
||||
{ label: 'List Cards', id: 'trello_list_cards' },
|
||||
{ label: 'Create Card', id: 'trello_create_card' },
|
||||
{ label: 'Update Card', id: 'trello_update_card' },
|
||||
{ label: 'Get Actions', id: 'trello_get_actions' },
|
||||
{ label: 'Add Comment', id: 'trello_add_comment' },
|
||||
],
|
||||
value: () => 'trello_list_lists',
|
||||
},
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Trello Account',
|
||||
type: 'oauth-input',
|
||||
provider: 'trello',
|
||||
serviceId: 'trello',
|
||||
requiredScopes: ['read', 'write'],
|
||||
placeholder: 'Select Trello account',
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'boardId',
|
||||
title: 'Board',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter board ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_list_lists',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'boardId',
|
||||
title: 'Board',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter board ID or search for a board',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_list_cards',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'listId',
|
||||
title: 'List (Optional)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter list ID to filter cards by list',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_list_cards',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'boardId',
|
||||
title: 'Board',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter board ID or search for a board',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_create_card',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'listId',
|
||||
title: 'List',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter list ID or search for a list',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_create_card',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'name',
|
||||
title: 'Card Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter card name/title',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_create_card',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'desc',
|
||||
title: 'Description',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter card description (optional)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_create_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'pos',
|
||||
title: 'Position',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Top', id: 'top' },
|
||||
{ label: 'Bottom', id: 'bottom' },
|
||||
],
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_create_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'due',
|
||||
title: 'Due Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD or ISO 8601',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_create_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'labels',
|
||||
title: 'Labels',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated label IDs (optional)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_create_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'cardId',
|
||||
title: 'Card',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter card ID or search for a card',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_update_card',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'name',
|
||||
title: 'New Card Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter new card name (leave empty to keep current)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_update_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'desc',
|
||||
title: 'New Description',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter new description (leave empty to keep current)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_update_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'closed',
|
||||
title: 'Archive Card',
|
||||
type: 'switch',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_update_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'dueComplete',
|
||||
title: 'Mark Due Date Complete',
|
||||
type: 'switch',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_update_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'idList',
|
||||
title: 'Move to List',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter list ID to move card',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_update_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'due',
|
||||
title: 'Due Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD or ISO 8601',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_update_card',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'boardId',
|
||||
title: 'Board ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter board ID to get board actions',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_get_actions',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cardId',
|
||||
title: 'Card ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter card ID to get card actions',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_get_actions',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'Action Filter',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., commentCard,updateCard',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_get_actions',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '50',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_get_actions',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cardId',
|
||||
title: 'Card',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter card ID or search for a card',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_add_comment',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'text',
|
||||
title: 'Comment',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter your comment',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'trello_add_comment',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'trello_list_lists',
|
||||
'trello_list_cards',
|
||||
'trello_create_card',
|
||||
'trello_update_card',
|
||||
'trello_get_actions',
|
||||
'trello_add_comment',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'trello_list_lists':
|
||||
return 'trello_list_lists'
|
||||
case 'trello_list_cards':
|
||||
return 'trello_list_cards'
|
||||
case 'trello_create_card':
|
||||
return 'trello_create_card'
|
||||
case 'trello_update_card':
|
||||
return 'trello_update_card'
|
||||
case 'trello_get_actions':
|
||||
return 'trello_get_actions'
|
||||
case 'trello_add_comment':
|
||||
return 'trello_add_comment'
|
||||
default:
|
||||
return 'trello_list_lists'
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { operation, limit, closed, dueComplete, ...rest } = params
|
||||
|
||||
const result: Record<string, any> = { ...rest }
|
||||
|
||||
if (limit && operation === 'trello_get_actions') {
|
||||
result.limit = Number.parseInt(limit, 10)
|
||||
}
|
||||
|
||||
if (closed !== undefined && operation === 'trello_update_card') {
|
||||
if (typeof closed === 'string') {
|
||||
result.closed = closed.toLowerCase() === 'true' || closed === '1'
|
||||
} else if (typeof closed === 'number') {
|
||||
result.closed = closed !== 0
|
||||
} else {
|
||||
result.closed = Boolean(closed)
|
||||
}
|
||||
}
|
||||
|
||||
if (dueComplete !== undefined && operation === 'trello_update_card') {
|
||||
if (typeof dueComplete === 'string') {
|
||||
result.dueComplete = dueComplete.toLowerCase() === 'true' || dueComplete === '1'
|
||||
} else if (typeof dueComplete === 'number') {
|
||||
result.dueComplete = dueComplete !== 0
|
||||
} else {
|
||||
result.dueComplete = Boolean(dueComplete)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Trello operation to perform' },
|
||||
credential: { type: 'string', description: 'Trello OAuth credential' },
|
||||
boardId: { type: 'string', description: 'Board ID' },
|
||||
listId: { type: 'string', description: 'List ID' },
|
||||
cardId: { type: 'string', description: 'Card ID' },
|
||||
name: { type: 'string', description: 'Card name/title' },
|
||||
desc: { type: 'string', description: 'Card or board description' },
|
||||
pos: { type: 'string', description: 'Card position (top, bottom, or number)' },
|
||||
due: { type: 'string', description: 'Due date in ISO 8601 format' },
|
||||
labels: { type: 'string', description: 'Comma-separated label IDs' },
|
||||
closed: { type: 'boolean', description: 'Archive/close status' },
|
||||
idList: { type: 'string', description: 'ID of list to move card to' },
|
||||
dueComplete: { type: 'boolean', description: 'Mark due date as complete' },
|
||||
filter: { type: 'string', description: 'Action type filter' },
|
||||
limit: { type: 'number', description: 'Maximum number of results' },
|
||||
text: { type: 'string', description: 'Comment text' },
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Whether the operation was successful' },
|
||||
lists: {
|
||||
type: 'array',
|
||||
description: 'Array of list objects (for list_lists operation)',
|
||||
},
|
||||
cards: {
|
||||
type: 'array',
|
||||
description: 'Array of card objects (for list_cards operation)',
|
||||
},
|
||||
card: {
|
||||
type: 'json',
|
||||
description: 'Card object (for create_card and update_card operations)',
|
||||
},
|
||||
actions: {
|
||||
type: 'array',
|
||||
description: 'Array of action objects (for get_actions operation)',
|
||||
},
|
||||
comment: {
|
||||
type: 'json',
|
||||
description: 'Comment object (for add_comment operation)',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Number of items returned (boards, cards, actions)',
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
description: 'Error message if operation failed',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export const XBlock: BlockConfig<XResponse> = {
|
||||
type: 'oauth-input',
|
||||
provider: 'x',
|
||||
serviceId: 'x',
|
||||
requiredScopes: ['tweet.read', 'tweet.write', 'users.read'],
|
||||
requiredScopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
|
||||
placeholder: 'Select X account',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AirtableBlock } from '@/blocks/blocks/airtable'
|
||||
import { ApiBlock } from '@/blocks/blocks/api'
|
||||
import { ApiTriggerBlock } from '@/blocks/blocks/api_trigger'
|
||||
import { ArxivBlock } from '@/blocks/blocks/arxiv'
|
||||
import { AsanaBlock } from '@/blocks/blocks/asana'
|
||||
import { BrowserUseBlock } from '@/blocks/blocks/browser_use'
|
||||
import { ChatTriggerBlock } from '@/blocks/blocks/chat_trigger'
|
||||
import { ClayBlock } from '@/blocks/blocks/clay'
|
||||
@@ -26,6 +27,7 @@ import { GoogleFormsBlock } from '@/blocks/blocks/google_form'
|
||||
import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets'
|
||||
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
|
||||
import { GuardrailsBlock } from '@/blocks/blocks/guardrails'
|
||||
import { HubSpotBlock } from '@/blocks/blocks/hubspot'
|
||||
import { HuggingFaceBlock } from '@/blocks/blocks/huggingface'
|
||||
import { HunterBlock } from '@/blocks/blocks/hunter'
|
||||
import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator'
|
||||
@@ -53,6 +55,7 @@ import { ParallelBlock } from '@/blocks/blocks/parallel'
|
||||
import { PauseResumeBlock } from '@/blocks/blocks/pause_resume'
|
||||
import { PerplexityBlock } from '@/blocks/blocks/perplexity'
|
||||
import { PineconeBlock } from '@/blocks/blocks/pinecone'
|
||||
import { PipedriveBlock } from '@/blocks/blocks/pipedrive'
|
||||
import { PostgreSQLBlock } from '@/blocks/blocks/postgresql'
|
||||
import { QdrantBlock } from '@/blocks/blocks/qdrant'
|
||||
import { RedditBlock } from '@/blocks/blocks/reddit'
|
||||
@@ -60,6 +63,7 @@ import { ResendBlock } from '@/blocks/blocks/resend'
|
||||
import { ResponseBlock } from '@/blocks/blocks/response'
|
||||
import { RouterBlock } from '@/blocks/blocks/router'
|
||||
import { S3Block } from '@/blocks/blocks/s3'
|
||||
import { SalesforceBlock } from '@/blocks/blocks/salesforce'
|
||||
import { ScheduleBlock } from '@/blocks/blocks/schedule'
|
||||
import { SerperBlock } from '@/blocks/blocks/serper'
|
||||
import { SharepointBlock } from '@/blocks/blocks/sharepoint'
|
||||
@@ -74,6 +78,7 @@ import { TavilyBlock } from '@/blocks/blocks/tavily'
|
||||
import { TelegramBlock } from '@/blocks/blocks/telegram'
|
||||
import { ThinkingBlock } from '@/blocks/blocks/thinking'
|
||||
import { TranslateBlock } from '@/blocks/blocks/translate'
|
||||
import { TrelloBlock } from '@/blocks/blocks/trello'
|
||||
import { TwilioSMSBlock } from '@/blocks/blocks/twilio'
|
||||
import { TwilioVoiceBlock } from '@/blocks/blocks/twilio_voice'
|
||||
import { TypeformBlock } from '@/blocks/blocks/typeform'
|
||||
@@ -99,6 +104,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
api: ApiBlock,
|
||||
approval: PauseResumeBlock,
|
||||
arxiv: ArxivBlock,
|
||||
asana: AsanaBlock,
|
||||
browser_use: BrowserUseBlock,
|
||||
clay: ClayBlock,
|
||||
condition: ConditionBlock,
|
||||
@@ -121,6 +127,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
google_search: GoogleSearchBlock,
|
||||
google_sheets: GoogleSheetsBlock,
|
||||
google_vault: GoogleVaultBlock,
|
||||
hubspot: HubSpotBlock,
|
||||
huggingface: HuggingFaceBlock,
|
||||
hunter: HunterBlock,
|
||||
image_generator: ImageGeneratorBlock,
|
||||
@@ -145,6 +152,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
parallel_ai: ParallelBlock,
|
||||
perplexity: PerplexityBlock,
|
||||
pinecone: PineconeBlock,
|
||||
pipedrive: PipedriveBlock,
|
||||
postgresql: PostgreSQLBlock,
|
||||
qdrant: QdrantBlock,
|
||||
resend: ResendBlock,
|
||||
@@ -154,6 +162,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
router: RouterBlock,
|
||||
schedule: ScheduleBlock,
|
||||
s3: S3Block,
|
||||
salesforce: SalesforceBlock,
|
||||
serper: SerperBlock,
|
||||
sharepoint: SharepointBlock,
|
||||
// sms: SMSBlock,
|
||||
@@ -172,6 +181,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
telegram: TelegramBlock,
|
||||
thinking: ThinkingBlock,
|
||||
translate: TranslateBlock,
|
||||
trello: TrelloBlock,
|
||||
twilio_sms: TwilioSMSBlock,
|
||||
twilio_voice: TwilioVoiceBlock,
|
||||
typeform: TypeformBlock,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -153,6 +153,9 @@ export const auth = betterAuth({
|
||||
'slack',
|
||||
'reddit',
|
||||
'webflow',
|
||||
'asana',
|
||||
'pipedrive',
|
||||
'hubspot',
|
||||
|
||||
// Common SSO provider patterns
|
||||
...SSO_TRUSTED_PROVIDERS,
|
||||
@@ -661,6 +664,208 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'pipedrive',
|
||||
clientId: env.PIPEDRIVE_CLIENT_ID as string,
|
||||
clientSecret: env.PIPEDRIVE_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://oauth.pipedrive.com/oauth/authorize',
|
||||
tokenUrl: 'https://oauth.pipedrive.com/oauth/token',
|
||||
userInfoUrl: 'https://api.pipedrive.com/v1/users/me',
|
||||
prompt: 'consent',
|
||||
scopes: [
|
||||
'base',
|
||||
'deals:full',
|
||||
'contacts:full',
|
||||
'leads:full',
|
||||
'activities:full',
|
||||
'mail:full',
|
||||
'projects:full',
|
||||
],
|
||||
responseType: 'code',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/pipedrive`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
logger.info('Fetching Pipedrive user profile')
|
||||
|
||||
const response = await fetch('https://api.pipedrive.com/v1/users/me', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Failed to fetch Pipedrive user info', {
|
||||
status: response.status,
|
||||
})
|
||||
throw new Error('Failed to fetch user info')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const user = data.data
|
||||
|
||||
return {
|
||||
id: user.id.toString(),
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
emailVerified: user.activated,
|
||||
image: user.icon_url,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error creating Pipedrive user profile:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// HubSpot provider
|
||||
{
|
||||
providerId: 'hubspot',
|
||||
clientId: env.HUBSPOT_CLIENT_ID as string,
|
||||
clientSecret: env.HUBSPOT_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://app.hubspot.com/oauth/authorize',
|
||||
tokenUrl: 'https://api.hubapi.com/oauth/v1/token',
|
||||
userInfoUrl: 'https://api.hubapi.com/oauth/v1/access-tokens',
|
||||
prompt: 'consent',
|
||||
scopes: [
|
||||
'crm.objects.contacts.read',
|
||||
'crm.objects.contacts.write',
|
||||
'crm.objects.companies.read',
|
||||
'crm.objects.companies.write',
|
||||
'crm.objects.deals.read',
|
||||
'crm.objects.deals.write',
|
||||
'crm.objects.owners.read',
|
||||
'crm.objects.users.read',
|
||||
'crm.objects.users.write',
|
||||
'crm.objects.marketing_events.read',
|
||||
'crm.objects.marketing_events.write',
|
||||
'crm.objects.line_items.read',
|
||||
'crm.objects.line_items.write',
|
||||
'crm.objects.quotes.read',
|
||||
'crm.objects.quotes.write',
|
||||
'crm.objects.appointments.read',
|
||||
'crm.objects.appointments.write',
|
||||
'crm.objects.carts.read',
|
||||
'crm.objects.carts.write',
|
||||
'crm.import',
|
||||
'crm.lists.read',
|
||||
'crm.lists.write',
|
||||
'tickets',
|
||||
],
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/hubspot`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
logger.info('Fetching HubSpot user profile')
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.hubapi.com/oauth/v1/access-tokens/${tokens.accessToken}`
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
let errorBody: string | undefined
|
||||
try {
|
||||
errorBody = await response.text()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
logger.error('Failed to fetch HubSpot user info', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: errorBody?.slice(0, 500),
|
||||
})
|
||||
throw new Error('Failed to fetch user info')
|
||||
}
|
||||
|
||||
const rawText = await response.text()
|
||||
const data = JSON.parse(rawText)
|
||||
|
||||
const scopesArray = Array.isArray((data as any)?.scopes) ? (data as any).scopes : []
|
||||
if (Array.isArray(scopesArray) && scopesArray.length > 0) {
|
||||
tokens.scopes = scopesArray
|
||||
} else if (typeof (data as any)?.scope === 'string') {
|
||||
tokens.scopes = (data as any).scope.split(/\s+/).filter(Boolean)
|
||||
}
|
||||
|
||||
logger.info('HubSpot token metadata response:', {
|
||||
hasScopes: !!data.scopes,
|
||||
scopesType: typeof data.scopes,
|
||||
scopesIsArray: Array.isArray(data.scopes),
|
||||
scopesValue: data.scopes,
|
||||
fullResponse: data,
|
||||
})
|
||||
|
||||
return {
|
||||
id: data.user_id || data.hub_id.toString(),
|
||||
name: data.user || 'HubSpot User',
|
||||
email: data.user || `hubspot-${data.hub_id}@hubspot.com`,
|
||||
emailVerified: true,
|
||||
image: undefined,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
// Extract scopes from HubSpot's response and convert array to space-delimited string
|
||||
// Use 'scope' (singular) as that's what better-auth expects for the account table
|
||||
...(data.scopes && Array.isArray(data.scopes)
|
||||
? { scope: data.scopes.join(' ') }
|
||||
: {}),
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error creating HubSpot user profile:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Salesforce provider
|
||||
{
|
||||
providerId: 'salesforce',
|
||||
clientId: env.SALESFORCE_CLIENT_ID as string,
|
||||
clientSecret: env.SALESFORCE_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://login.salesforce.com/services/oauth2/authorize',
|
||||
tokenUrl: 'https://login.salesforce.com/services/oauth2/token',
|
||||
userInfoUrl: 'https://login.salesforce.com/services/oauth2/userinfo',
|
||||
scopes: ['api', 'refresh_token', 'openid'],
|
||||
pkce: true,
|
||||
prompt: 'consent',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/salesforce`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
logger.info('Fetching Salesforce user profile')
|
||||
|
||||
const response = await fetch(
|
||||
'https://login.salesforce.com/services/oauth2/userinfo',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Failed to fetch Salesforce user info', {
|
||||
status: response.status,
|
||||
})
|
||||
throw new Error('Failed to fetch user info')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
id: data.user_id || data.sub,
|
||||
name: data.name || 'Salesforce User',
|
||||
email: data.email || `salesforce-${data.user_id}@salesforce.com`,
|
||||
emailVerified: data.email_verified || true,
|
||||
image: data.picture || undefined,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error creating Salesforce user profile:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Supabase provider
|
||||
{
|
||||
providerId: 'supabase',
|
||||
@@ -1216,6 +1421,57 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'asana',
|
||||
clientId: env.ASANA_CLIENT_ID as string,
|
||||
clientSecret: env.ASANA_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://app.asana.com/-/oauth_authorize',
|
||||
tokenUrl: 'https://app.asana.com/-/oauth_token',
|
||||
userInfoUrl: 'https://app.asana.com/api/1.0/users/me',
|
||||
scopes: ['default'],
|
||||
responseType: 'code',
|
||||
pkce: false,
|
||||
accessType: 'offline',
|
||||
authentication: 'basic',
|
||||
prompt: 'consent',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/asana`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://app.asana.com/api/1.0/users/me', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Error fetching Asana user info:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const profile = result.data
|
||||
|
||||
const now = new Date()
|
||||
|
||||
return {
|
||||
id: profile.gid,
|
||||
name: profile.name || 'Asana User',
|
||||
email: profile.email || `${profile.gid}@asana.user`,
|
||||
image: profile.photo?.image_128x128 || undefined,
|
||||
emailVerified: !!profile.email,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Asana getUserInfo:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Slack provider
|
||||
{
|
||||
providerId: 'slack',
|
||||
|
||||
@@ -181,6 +181,8 @@ export const env = createEnv({
|
||||
CONFLUENCE_CLIENT_SECRET: z.string().optional(), // Atlassian Confluence OAuth client secret
|
||||
JIRA_CLIENT_ID: z.string().optional(), // Atlassian Jira OAuth client ID
|
||||
JIRA_CLIENT_SECRET: z.string().optional(), // Atlassian Jira OAuth client secret
|
||||
ASANA_CLIENT_ID: z.string().optional(), // Asana OAuth client ID
|
||||
ASANA_CLIENT_SECRET: z.string().optional(), // Asana OAuth client secret
|
||||
AIRTABLE_CLIENT_ID: z.string().optional(), // Airtable OAuth client ID
|
||||
AIRTABLE_CLIENT_SECRET: z.string().optional(), // Airtable OAuth client secret
|
||||
SUPABASE_CLIENT_ID: z.string().optional(), // Supabase OAuth client ID
|
||||
@@ -193,8 +195,12 @@ export const env = createEnv({
|
||||
MICROSOFT_CLIENT_SECRET: z.string().optional(), // Microsoft OAuth client secret
|
||||
HUBSPOT_CLIENT_ID: z.string().optional(), // HubSpot OAuth client ID
|
||||
HUBSPOT_CLIENT_SECRET: z.string().optional(), // HubSpot OAuth client secret
|
||||
SALESFORCE_CLIENT_ID: z.string().optional(), // Salesforce OAuth client ID
|
||||
SALESFORCE_CLIENT_SECRET: z.string().optional(), // Salesforce OAuth client secret
|
||||
WEALTHBOX_CLIENT_ID: z.string().optional(), // WealthBox OAuth client ID
|
||||
WEALTHBOX_CLIENT_SECRET: z.string().optional(), // WealthBox OAuth client secret
|
||||
PIPEDRIVE_CLIENT_ID: z.string().optional(), // Pipedrive OAuth client ID
|
||||
PIPEDRIVE_CLIENT_SECRET: z.string().optional(), // Pipedrive OAuth client secret
|
||||
LINEAR_CLIENT_ID: z.string().optional(), // Linear OAuth client ID
|
||||
LINEAR_CLIENT_SECRET: z.string().optional(), // Linear OAuth client secret
|
||||
SLACK_CLIENT_ID: z.string().optional(), // Slack OAuth client ID
|
||||
@@ -203,6 +209,7 @@ export const env = createEnv({
|
||||
REDDIT_CLIENT_SECRET: z.string().optional(), // Reddit OAuth client secret
|
||||
WEBFLOW_CLIENT_ID: z.string().optional(), // Webflow OAuth client ID
|
||||
WEBFLOW_CLIENT_SECRET: z.string().optional(), // Webflow OAuth client secret
|
||||
TRELLO_API_KEY: z.string().optional(), // Trello API Key
|
||||
|
||||
// E2B Remote Code Execution
|
||||
E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution
|
||||
@@ -249,7 +256,7 @@ export const env = createEnv({
|
||||
|
||||
client: {
|
||||
// Core Application URLs - Required for frontend functionality
|
||||
NEXT_PUBLIC_APP_URL: z.string().url(), // Base URL of the application (e.g., https://app.sim.ai)
|
||||
NEXT_PUBLIC_APP_URL: z.string().url(), // Base URL of the application (e.g., https://www.sim.ai)
|
||||
|
||||
// Client-side Services
|
||||
NEXT_PUBLIC_SOCKET_URL: z.string().url().optional(), // WebSocket server URL for real-time features
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import {
|
||||
AirtableIcon,
|
||||
AsanaIcon,
|
||||
ConfluenceIcon,
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
GoogleFormsIcon,
|
||||
GoogleIcon,
|
||||
GoogleSheetsIcon,
|
||||
HubspotIcon,
|
||||
JiraIcon,
|
||||
LinearIcon,
|
||||
MicrosoftExcelIcon,
|
||||
@@ -21,9 +23,12 @@ import {
|
||||
MicrosoftTeamsIcon,
|
||||
NotionIcon,
|
||||
OutlookIcon,
|
||||
PipedriveIcon,
|
||||
RedditIcon,
|
||||
SalesforceIcon,
|
||||
SlackIcon,
|
||||
SupabaseIcon,
|
||||
TrelloIcon,
|
||||
WealthboxIcon,
|
||||
WebflowIcon,
|
||||
xIcon,
|
||||
@@ -47,8 +52,13 @@ export type OAuthProvider =
|
||||
| 'linear'
|
||||
| 'slack'
|
||||
| 'reddit'
|
||||
| 'trello'
|
||||
| 'wealthbox'
|
||||
| 'webflow'
|
||||
| 'asana'
|
||||
| 'pipedrive'
|
||||
| 'hubspot'
|
||||
| 'salesforce'
|
||||
| string
|
||||
|
||||
export type OAuthService =
|
||||
@@ -79,7 +89,11 @@ export type OAuthService =
|
||||
| 'wealthbox'
|
||||
| 'onedrive'
|
||||
| 'webflow'
|
||||
|
||||
| 'trello'
|
||||
| 'asana'
|
||||
| 'pipedrive'
|
||||
| 'hubspot'
|
||||
| 'salesforce'
|
||||
export interface OAuthProviderConfig {
|
||||
id: OAuthProvider
|
||||
name: string
|
||||
@@ -244,17 +258,19 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
'Chat.Read',
|
||||
'Chat.ReadWrite',
|
||||
'Chat.ReadBasic',
|
||||
'ChatMessage.Send',
|
||||
'Channel.ReadBasic.All',
|
||||
'ChannelMessage.Send',
|
||||
'ChannelMessage.Read.All',
|
||||
'ChannelMessage.ReadWrite',
|
||||
'ChannelMember.Read.All',
|
||||
'Group.Read.All',
|
||||
'Group.ReadWrite.All',
|
||||
'Team.ReadBasic.All',
|
||||
'TeamMember.Read.All',
|
||||
'offline_access',
|
||||
'Files.Read',
|
||||
'Sites.Read.All',
|
||||
'TeamMember.Read.All',
|
||||
],
|
||||
},
|
||||
outlook: {
|
||||
@@ -383,12 +399,10 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
'delete:attachment:confluence',
|
||||
'read:content:confluence',
|
||||
'delete:page:confluence',
|
||||
'write:label:confluence',
|
||||
'read:label:confluence',
|
||||
'write:label:confluence',
|
||||
'read:attachment:confluence',
|
||||
'write:attachment:confluence',
|
||||
'read:label:confluence',
|
||||
'write:label:confluence',
|
||||
'search:confluence',
|
||||
'read:me',
|
||||
'offline_access',
|
||||
@@ -465,7 +479,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
providerId: 'airtable',
|
||||
icon: (props) => AirtableIcon(props),
|
||||
baseProviderIcon: (props) => AirtableIcon(props),
|
||||
scopes: ['data.records:read', 'data.records:write'],
|
||||
scopes: ['data.records:read', 'data.records:write', 'user.email:read', 'webhook:manage'],
|
||||
},
|
||||
},
|
||||
defaultService: 'airtable',
|
||||
@@ -618,6 +632,123 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
},
|
||||
defaultService: 'webflow',
|
||||
},
|
||||
trello: {
|
||||
id: 'trello',
|
||||
name: 'Trello',
|
||||
icon: (props) => TrelloIcon(props),
|
||||
services: {
|
||||
trello: {
|
||||
id: 'trello',
|
||||
name: 'Trello',
|
||||
description: 'Manage Trello boards, cards, and workflows.',
|
||||
providerId: 'trello',
|
||||
icon: (props) => TrelloIcon(props),
|
||||
baseProviderIcon: (props) => TrelloIcon(props),
|
||||
scopes: ['read', 'write'],
|
||||
},
|
||||
},
|
||||
defaultService: 'trello',
|
||||
},
|
||||
asana: {
|
||||
id: 'asana',
|
||||
name: 'Asana',
|
||||
icon: (props) => AsanaIcon(props),
|
||||
services: {
|
||||
asana: {
|
||||
id: 'asana',
|
||||
name: 'Asana',
|
||||
description: 'Manage Asana projects, tasks, and workflows.',
|
||||
providerId: 'asana',
|
||||
icon: (props) => AsanaIcon(props),
|
||||
baseProviderIcon: (props) => AsanaIcon(props),
|
||||
scopes: ['default'],
|
||||
},
|
||||
},
|
||||
defaultService: 'asana',
|
||||
},
|
||||
pipedrive: {
|
||||
id: 'pipedrive',
|
||||
name: 'Pipedrive',
|
||||
icon: (props) => PipedriveIcon(props),
|
||||
services: {
|
||||
pipedrive: {
|
||||
id: 'pipedrive',
|
||||
name: 'Pipedrive',
|
||||
description: 'Manage deals, contacts, and sales pipeline in Pipedrive CRM.',
|
||||
providerId: 'pipedrive',
|
||||
icon: (props) => PipedriveIcon(props),
|
||||
baseProviderIcon: (props) => PipedriveIcon(props),
|
||||
scopes: [
|
||||
'base',
|
||||
'deals:full',
|
||||
'contacts:full',
|
||||
'leads:full',
|
||||
'activities:full',
|
||||
'mail:full',
|
||||
'projects:full',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultService: 'pipedrive',
|
||||
},
|
||||
hubspot: {
|
||||
id: 'hubspot',
|
||||
name: 'HubSpot',
|
||||
icon: (props) => HubspotIcon(props),
|
||||
services: {
|
||||
hubspot: {
|
||||
id: 'hubspot',
|
||||
name: 'HubSpot',
|
||||
description: 'Access and manage your HubSpot CRM data.',
|
||||
providerId: 'hubspot',
|
||||
icon: (props) => HubspotIcon(props),
|
||||
baseProviderIcon: (props) => HubspotIcon(props),
|
||||
scopes: [
|
||||
'crm.objects.contacts.read',
|
||||
'crm.objects.contacts.write',
|
||||
'crm.objects.companies.read',
|
||||
'crm.objects.companies.write',
|
||||
'crm.objects.deals.read',
|
||||
'crm.objects.deals.write',
|
||||
'crm.objects.owners.read',
|
||||
'crm.objects.users.read',
|
||||
'crm.objects.users.write',
|
||||
'crm.objects.marketing_events.read',
|
||||
'crm.objects.marketing_events.write',
|
||||
'crm.objects.line_items.read',
|
||||
'crm.objects.line_items.write',
|
||||
'crm.objects.quotes.read',
|
||||
'crm.objects.quotes.write',
|
||||
'crm.objects.appointments.read',
|
||||
'crm.objects.appointments.write',
|
||||
'crm.objects.carts.read',
|
||||
'crm.objects.carts.write',
|
||||
'crm.import',
|
||||
'crm.lists.read',
|
||||
'crm.lists.write',
|
||||
'tickets',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultService: 'hubspot',
|
||||
},
|
||||
salesforce: {
|
||||
id: 'salesforce',
|
||||
name: 'Salesforce',
|
||||
icon: (props) => SalesforceIcon(props),
|
||||
services: {
|
||||
salesforce: {
|
||||
id: 'salesforce',
|
||||
name: 'Salesforce',
|
||||
description: 'Access and manage your Salesforce CRM data.',
|
||||
providerId: 'salesforce',
|
||||
icon: (props) => SalesforceIcon(props),
|
||||
baseProviderIcon: (props) => SalesforceIcon(props),
|
||||
scopes: ['api', 'refresh_token', 'openid'],
|
||||
},
|
||||
},
|
||||
defaultService: 'salesforce',
|
||||
},
|
||||
}
|
||||
|
||||
export function getServiceByProviderAndId(
|
||||
@@ -1031,6 +1162,58 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
|
||||
supportsRefreshTokenRotation: false,
|
||||
}
|
||||
}
|
||||
case 'asana': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.ASANA_CLIENT_ID,
|
||||
env.ASANA_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://app.asana.com/-/oauth_token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: true,
|
||||
supportsRefreshTokenRotation: true,
|
||||
}
|
||||
}
|
||||
case 'pipedrive': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.PIPEDRIVE_CLIENT_ID,
|
||||
env.PIPEDRIVE_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://oauth.pipedrive.com/oauth/token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: false,
|
||||
supportsRefreshTokenRotation: true,
|
||||
}
|
||||
}
|
||||
case 'hubspot': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.HUBSPOT_CLIENT_ID,
|
||||
env.HUBSPOT_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://api.hubapi.com/oauth/v1/token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: false,
|
||||
supportsRefreshTokenRotation: true,
|
||||
}
|
||||
}
|
||||
case 'salesforce': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.SALESFORCE_CLIENT_ID,
|
||||
env.SALESFORCE_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://login.salesforce.com/services/oauth2/token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: false,
|
||||
supportsRefreshTokenRotation: false,
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provider: ${provider}`)
|
||||
}
|
||||
|
||||
@@ -738,6 +738,47 @@ export async function queueWebhookExecution(
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'hubspot') {
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const triggerId = providerConfig.triggerId as string | undefined
|
||||
|
||||
if (triggerId?.startsWith('hubspot_')) {
|
||||
const events = Array.isArray(body) ? body : [body]
|
||||
const firstEvent = events[0]
|
||||
|
||||
const subscriptionType = firstEvent?.subscriptionType as string | undefined
|
||||
|
||||
const { isHubSpotContactEventMatch } = await import('@/triggers/hubspot/utils')
|
||||
|
||||
if (!isHubSpotContactEventMatch(triggerId, subscriptionType || '')) {
|
||||
logger.debug(
|
||||
`[${options.requestId}] HubSpot event mismatch for trigger ${triggerId}. Event: ${subscriptionType}. Skipping execution.`,
|
||||
{
|
||||
webhookId: foundWebhook.id,
|
||||
workflowId: foundWorkflow.id,
|
||||
triggerId,
|
||||
receivedEvent: subscriptionType,
|
||||
}
|
||||
)
|
||||
|
||||
// Return 200 OK to prevent HubSpot from retrying
|
||||
return NextResponse.json({
|
||||
message: 'Event type does not match trigger configuration. Ignoring.',
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${options.requestId}] HubSpot event match confirmed for trigger ${triggerId}. Event: ${subscriptionType}`,
|
||||
{
|
||||
webhookId: foundWebhook.id,
|
||||
workflowId: foundWorkflow.id,
|
||||
triggerId,
|
||||
receivedEvent: subscriptionType,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const headers = Object.fromEntries(request.headers.entries())
|
||||
|
||||
// For Microsoft Teams Graph notifications, extract unique identifiers for idempotency
|
||||
|
||||
@@ -783,18 +783,41 @@ export async function formatWebhookInput(
|
||||
|
||||
if (foundWebhook.provider === 'gmail') {
|
||||
if (body && typeof body === 'object' && 'email' in body) {
|
||||
return body // { email: {...}, timestamp: ... }
|
||||
return body
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'outlook') {
|
||||
if (body && typeof body === 'object' && 'email' in body) {
|
||||
return body // { email: {...}, timestamp: ... }
|
||||
return body
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'hubspot') {
|
||||
const events = Array.isArray(body) ? body : [body]
|
||||
const event = events[0]
|
||||
|
||||
if (!event) {
|
||||
logger.warn('HubSpot webhook received with empty payload')
|
||||
return null
|
||||
}
|
||||
|
||||
logger.info('Formatting HubSpot webhook input', {
|
||||
subscriptionType: event.subscriptionType,
|
||||
objectId: event.objectId,
|
||||
portalId: event.portalId,
|
||||
})
|
||||
|
||||
return {
|
||||
payload: body,
|
||||
provider: 'hubspot',
|
||||
providerConfig: foundWebhook.providerConfig,
|
||||
workflowId: foundWorkflow.id,
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'microsoftteams') {
|
||||
// Check if this is a Microsoft Graph change notification
|
||||
if (body?.value && Array.isArray(body.value) && body.value.length > 0) {
|
||||
|
||||
@@ -23,7 +23,7 @@ const createMockHeaders = (customHeaders: Record<string, string> = {}) => {
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
Referer: 'https://app.simstudio.dev',
|
||||
Referer: 'https://www.simstudio.dev',
|
||||
'Sec-Ch-Ua': 'Chromium;v=91, Not-A.Brand;v=99',
|
||||
'Sec-Ch-Ua-Mobile': '?0',
|
||||
'Sec-Ch-Ua-Platform': '"macOS"',
|
||||
|
||||
83
apps/sim/tools/asana/add_comment.ts
Normal file
83
apps/sim/tools/asana/add_comment.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { AsanaAddCommentParams, AsanaAddCommentResponse } from '@/tools/asana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const asanaAddCommentTool: ToolConfig<AsanaAddCommentParams, AsanaAddCommentResponse> = {
|
||||
id: 'asana_add_comment',
|
||||
name: 'Asana Add Comment',
|
||||
description: 'Add a comment (story) to an Asana task',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'asana',
|
||||
additionalScopes: [],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Asana',
|
||||
},
|
||||
taskGid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The globally unique identifier (GID) of the task',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The text content of the comment',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/asana/add-comment',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
taskGid: params.taskGid,
|
||||
text: params.text,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Empty response from Asana',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || null,
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Operation success status',
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Comment details including gid, text, created timestamp, and author',
|
||||
},
|
||||
},
|
||||
}
|
||||
120
apps/sim/tools/asana/create_task.ts
Normal file
120
apps/sim/tools/asana/create_task.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import type { AsanaCreateTaskParams, AsanaCreateTaskResponse } from '@/tools/asana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const asanaCreateTaskTool: ToolConfig<AsanaCreateTaskParams, AsanaCreateTaskResponse> = {
|
||||
id: 'asana_create_task',
|
||||
name: 'Asana Create Task',
|
||||
description: 'Create a new task in Asana',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'asana',
|
||||
additionalScopes: [],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Asana',
|
||||
},
|
||||
workspace: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workspace GID where the task will be created',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the task',
|
||||
},
|
||||
notes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Notes or description for the task',
|
||||
},
|
||||
assignee: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User GID to assign the task to',
|
||||
},
|
||||
due_on: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Due date in YYYY-MM-DD format',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/asana/create-task',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
workspace: params.workspace,
|
||||
name: params.name,
|
||||
notes: params.notes,
|
||||
assignee: params.assignee,
|
||||
due_on: params.due_on,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
gid: 'unknown',
|
||||
name: 'Task created successfully',
|
||||
notes: '',
|
||||
completed: false,
|
||||
created_at: new Date().toISOString(),
|
||||
permalink_url: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
gid: 'unknown',
|
||||
name: 'Task creation failed',
|
||||
notes: '',
|
||||
completed: false,
|
||||
created_at: new Date().toISOString(),
|
||||
permalink_url: '',
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Operation success status',
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created task details with timestamp, gid, name, notes, and permalink',
|
||||
},
|
||||
},
|
||||
}
|
||||
76
apps/sim/tools/asana/get_projects.ts
Normal file
76
apps/sim/tools/asana/get_projects.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { AsanaGetProjectsParams, AsanaGetProjectsResponse } from '@/tools/asana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const asanaGetProjectsTool: ToolConfig<AsanaGetProjectsParams, AsanaGetProjectsResponse> = {
|
||||
id: 'asana_get_projects',
|
||||
name: 'Asana Get Projects',
|
||||
description: 'Retrieve all projects from an Asana workspace',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'asana',
|
||||
additionalScopes: [],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Asana',
|
||||
},
|
||||
workspace: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workspace GID to retrieve projects from',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/asana/get-projects',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
workspace: params.workspace,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Empty response from Asana',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || { ts: new Date().toISOString(), projects: [] },
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Operation success status',
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'List of projects with their gid, name, and resource type',
|
||||
},
|
||||
},
|
||||
}
|
||||
99
apps/sim/tools/asana/get_task.ts
Normal file
99
apps/sim/tools/asana/get_task.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { AsanaGetTaskParams, AsanaGetTaskResponse } from '@/tools/asana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const asanaGetTaskTool: ToolConfig<AsanaGetTaskParams, AsanaGetTaskResponse> = {
|
||||
id: 'asana_get_task',
|
||||
name: 'Asana Get Task',
|
||||
description: 'Retrieve a single task by GID or get multiple tasks with filters',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'asana',
|
||||
additionalScopes: [],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Asana',
|
||||
},
|
||||
taskGid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The globally unique identifier (GID) of the task. If not provided, will get multiple tasks.',
|
||||
},
|
||||
workspace: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Workspace GID to filter tasks (required when not using taskGid)',
|
||||
},
|
||||
project: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Project GID to filter tasks',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of tasks to return (default: 50)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/asana/get-task',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
taskGid: params.taskGid,
|
||||
workspace: params.workspace,
|
||||
project: params.project,
|
||||
limit: params.limit,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Empty response from Asana',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || null,
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Operation success status',
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Single task details or array of tasks, depending on whether taskGid was provided',
|
||||
},
|
||||
},
|
||||
}
|
||||
13
apps/sim/tools/asana/index.ts
Normal file
13
apps/sim/tools/asana/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { asanaAddCommentTool } from '@/tools/asana/add_comment'
|
||||
import { asanaCreateTaskTool } from '@/tools/asana/create_task'
|
||||
import { asanaGetProjectsTool } from '@/tools/asana/get_projects'
|
||||
import { asanaGetTaskTool } from '@/tools/asana/get_task'
|
||||
import { asanaSearchTasksTool } from '@/tools/asana/search_tasks'
|
||||
import { asanaUpdateTaskTool } from '@/tools/asana/update_task'
|
||||
|
||||
export { asanaGetTaskTool }
|
||||
export { asanaCreateTaskTool }
|
||||
export { asanaUpdateTaskTool }
|
||||
export { asanaGetProjectsTool }
|
||||
export { asanaSearchTasksTool }
|
||||
export { asanaAddCommentTool }
|
||||
104
apps/sim/tools/asana/search_tasks.ts
Normal file
104
apps/sim/tools/asana/search_tasks.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { AsanaSearchTasksParams, AsanaSearchTasksResponse } from '@/tools/asana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const asanaSearchTasksTool: ToolConfig<AsanaSearchTasksParams, AsanaSearchTasksResponse> = {
|
||||
id: 'asana_search_tasks',
|
||||
name: 'Asana Search Tasks',
|
||||
description: 'Search for tasks in an Asana workspace',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'asana',
|
||||
additionalScopes: [],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Asana',
|
||||
},
|
||||
workspace: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workspace GID to search tasks in',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Text to search for in task names',
|
||||
},
|
||||
assignee: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter tasks by assignee user GID',
|
||||
},
|
||||
projects: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Array of project GIDs to filter tasks by',
|
||||
},
|
||||
completed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by completion status',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/asana/search-tasks',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
workspace: params.workspace,
|
||||
text: params.text,
|
||||
assignee: params.assignee,
|
||||
projects: params.projects,
|
||||
completed: params.completed,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Empty response from Asana',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || { ts: new Date().toISOString(), tasks: [] },
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Operation success status',
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'List of tasks matching the search criteria',
|
||||
},
|
||||
},
|
||||
}
|
||||
214
apps/sim/tools/asana/types.ts
Normal file
214
apps/sim/tools/asana/types.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface AsanaGetTaskParams {
|
||||
accessToken: string
|
||||
taskGid?: string
|
||||
workspace?: string
|
||||
project?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface AsanaGetTaskResponse extends ToolResponse {
|
||||
output:
|
||||
| {
|
||||
ts: string
|
||||
gid: string
|
||||
resource_type: string
|
||||
resource_subtype: string
|
||||
name: string
|
||||
notes: string
|
||||
completed: boolean
|
||||
assignee?: {
|
||||
gid: string
|
||||
name: string
|
||||
}
|
||||
created_by?: {
|
||||
gid: string
|
||||
resource_type: string
|
||||
name: string
|
||||
}
|
||||
due_on?: string
|
||||
created_at: string
|
||||
modified_at: string
|
||||
}
|
||||
| {
|
||||
ts: string
|
||||
tasks: Array<{
|
||||
gid: string
|
||||
resource_type: string
|
||||
resource_subtype: string
|
||||
name: string
|
||||
notes?: string
|
||||
completed: boolean
|
||||
assignee?: {
|
||||
gid: string
|
||||
name: string
|
||||
}
|
||||
created_by?: {
|
||||
gid: string
|
||||
resource_type: string
|
||||
name: string
|
||||
}
|
||||
due_on?: string
|
||||
created_at: string
|
||||
modified_at: string
|
||||
}>
|
||||
next_page?: {
|
||||
offset: string
|
||||
path: string
|
||||
uri: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface AsanaCreateTaskParams {
|
||||
accessToken: string
|
||||
workspace: string
|
||||
name: string
|
||||
notes?: string
|
||||
assignee?: string
|
||||
due_on?: string
|
||||
}
|
||||
|
||||
export interface AsanaCreateTaskResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
gid: string
|
||||
name: string
|
||||
notes: string
|
||||
completed: boolean
|
||||
created_at: string
|
||||
permalink_url: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AsanaUpdateTaskParams {
|
||||
accessToken: string
|
||||
taskGid: string
|
||||
name?: string
|
||||
notes?: string
|
||||
assignee?: string
|
||||
completed?: boolean
|
||||
due_on?: string
|
||||
}
|
||||
|
||||
export interface AsanaUpdateTaskResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
gid: string
|
||||
name: string
|
||||
notes: string
|
||||
completed: boolean
|
||||
modified_at: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AsanaGetProjectsParams {
|
||||
accessToken: string
|
||||
workspace: string
|
||||
}
|
||||
|
||||
export interface AsanaGetProjectsResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
projects: Array<{
|
||||
gid: string
|
||||
name: string
|
||||
resource_type: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface AsanaSearchTasksParams {
|
||||
accessToken: string
|
||||
workspace: string
|
||||
text?: string
|
||||
assignee?: string
|
||||
projects?: string[]
|
||||
completed?: boolean
|
||||
}
|
||||
|
||||
export interface AsanaSearchTasksResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
tasks: Array<{
|
||||
gid: string
|
||||
resource_type: string
|
||||
resource_subtype: string
|
||||
name: string
|
||||
notes?: string
|
||||
completed: boolean
|
||||
assignee?: {
|
||||
gid: string
|
||||
name: string
|
||||
}
|
||||
created_by?: {
|
||||
gid: string
|
||||
resource_type: string
|
||||
name: string
|
||||
}
|
||||
due_on?: string
|
||||
created_at: string
|
||||
modified_at: string
|
||||
}>
|
||||
next_page?: {
|
||||
offset: string
|
||||
path: string
|
||||
uri: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface AsanaTask {
|
||||
gid: string
|
||||
resource_type: string
|
||||
resource_subtype: string
|
||||
name: string
|
||||
notes?: string
|
||||
completed: boolean
|
||||
assignee?: {
|
||||
gid: string
|
||||
name: string
|
||||
}
|
||||
created_by?: {
|
||||
gid: string
|
||||
resource_type: string
|
||||
name: string
|
||||
}
|
||||
due_on?: string
|
||||
created_at: string
|
||||
modified_at: string
|
||||
}
|
||||
|
||||
export interface AsanaProject {
|
||||
gid: string
|
||||
name: string
|
||||
resource_type: string
|
||||
}
|
||||
|
||||
export interface AsanaAddCommentParams {
|
||||
accessToken: string
|
||||
taskGid: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface AsanaAddCommentResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
gid: string
|
||||
text: string
|
||||
created_at: string
|
||||
created_by: {
|
||||
gid: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type AsanaResponse =
|
||||
| AsanaGetTaskResponse
|
||||
| AsanaCreateTaskResponse
|
||||
| AsanaUpdateTaskResponse
|
||||
| AsanaGetProjectsResponse
|
||||
| AsanaSearchTasksResponse
|
||||
| AsanaAddCommentResponse
|
||||
125
apps/sim/tools/asana/update_task.ts
Normal file
125
apps/sim/tools/asana/update_task.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import type { AsanaUpdateTaskParams, AsanaUpdateTaskResponse } from '@/tools/asana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const asanaUpdateTaskTool: ToolConfig<AsanaUpdateTaskParams, AsanaUpdateTaskResponse> = {
|
||||
id: 'asana_update_task',
|
||||
name: 'Asana Update Task',
|
||||
description: 'Update an existing task in Asana',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'asana',
|
||||
additionalScopes: [],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Asana',
|
||||
},
|
||||
taskGid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The globally unique identifier (GID) of the task to update',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated name for the task',
|
||||
},
|
||||
notes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated notes or description for the task',
|
||||
},
|
||||
assignee: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated assignee user GID',
|
||||
},
|
||||
completed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Mark task as completed or not completed',
|
||||
},
|
||||
due_on: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated due date in YYYY-MM-DD format',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/asana/update-task',
|
||||
method: 'PUT',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
taskGid: params.taskGid,
|
||||
name: params.name,
|
||||
notes: params.notes,
|
||||
assignee: params.assignee,
|
||||
completed: params.completed,
|
||||
due_on: params.due_on,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
gid: 'unknown',
|
||||
name: 'Task updated successfully',
|
||||
notes: '',
|
||||
completed: false,
|
||||
modified_at: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
gid: 'unknown',
|
||||
name: 'Task update failed',
|
||||
notes: '',
|
||||
completed: false,
|
||||
modified_at: new Date().toISOString(),
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Operation success status',
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Updated task details with timestamp, gid, name, notes, and modified timestamp',
|
||||
},
|
||||
},
|
||||
}
|
||||
110
apps/sim/tools/hubspot/create_company.ts
Normal file
110
apps/sim/tools/hubspot/create_company.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
HubSpotCreateCompanyParams,
|
||||
HubSpotCreateCompanyResponse,
|
||||
} from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotCreateCompany')
|
||||
|
||||
export const hubspotCreateCompanyTool: ToolConfig<
|
||||
HubSpotCreateCompanyParams,
|
||||
HubSpotCreateCompanyResponse
|
||||
> = {
|
||||
id: 'hubspot_create_company',
|
||||
name: 'Create Company in HubSpot',
|
||||
description: 'Create a new company in HubSpot',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Company properties as JSON object (e.g., name, domain, city, industry)',
|
||||
},
|
||||
associations: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Array of associations to create with the company',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.hubapi.com/crm/v3/objects/companies',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: any = {
|
||||
properties: params.properties,
|
||||
}
|
||||
|
||||
if (params.associations && params.associations.length > 0) {
|
||||
body.associations = params.associations
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to create company in HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
company: data,
|
||||
metadata: {
|
||||
operation: 'create_company' as const,
|
||||
companyId: data.id,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created company data',
|
||||
properties: {
|
||||
company: {
|
||||
type: 'object',
|
||||
description: 'Created company object with properties and ID',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
113
apps/sim/tools/hubspot/create_contact.ts
Normal file
113
apps/sim/tools/hubspot/create_contact.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
HubSpotCreateContactParams,
|
||||
HubSpotCreateContactResponse,
|
||||
} from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotCreateContact')
|
||||
|
||||
export const hubspotCreateContactTool: ToolConfig<
|
||||
HubSpotCreateContactParams,
|
||||
HubSpotCreateContactResponse
|
||||
> = {
|
||||
id: 'hubspot_create_contact',
|
||||
name: 'Create Contact in HubSpot',
|
||||
description:
|
||||
'Create a new contact in HubSpot. Requires at least one of: email, firstname, or lastname',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Contact properties as JSON object. Must include at least one of: email, firstname, or lastname',
|
||||
},
|
||||
associations: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Array of associations to create with the contact (e.g., companies, deals). Each object should have "to" (with "id") and "types" (with "associationCategory" and "associationTypeId")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.hubapi.com/crm/v3/objects/contacts',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: any = {
|
||||
properties: params.properties,
|
||||
}
|
||||
|
||||
if (params.associations && params.associations.length > 0) {
|
||||
body.associations = params.associations
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to create contact in HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contact: data,
|
||||
metadata: {
|
||||
operation: 'create_contact' as const,
|
||||
contactId: data.id,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created contact data',
|
||||
properties: {
|
||||
contact: {
|
||||
type: 'object',
|
||||
description: 'Created contact object with properties and ID',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
123
apps/sim/tools/hubspot/get_company.ts
Normal file
123
apps/sim/tools/hubspot/get_company.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { HubSpotGetCompanyParams, HubSpotGetCompanyResponse } from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotGetCompany')
|
||||
|
||||
export const hubspotGetCompanyTool: ToolConfig<HubSpotGetCompanyParams, HubSpotGetCompanyResponse> =
|
||||
{
|
||||
id: 'hubspot_get_company',
|
||||
name: 'Get Company from HubSpot',
|
||||
description: 'Retrieve a single company by ID or domain from HubSpot',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
companyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID or domain of the company to retrieve',
|
||||
},
|
||||
idProperty: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Property to use as unique identifier (e.g., "domain"). If not specified, uses record ID',
|
||||
},
|
||||
properties: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of properties to return',
|
||||
},
|
||||
associations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of object types to retrieve associated IDs for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.hubapi.com/crm/v3/objects/companies/${params.companyId}`
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.idProperty) {
|
||||
queryParams.append('idProperty', params.idProperty)
|
||||
}
|
||||
if (params.properties) {
|
||||
queryParams.append('properties', params.properties)
|
||||
}
|
||||
if (params.associations) {
|
||||
queryParams.append('associations', params.associations)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to get company from HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
company: data,
|
||||
metadata: {
|
||||
operation: 'get_company' as const,
|
||||
companyId: data.id,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Company data',
|
||||
properties: {
|
||||
company: {
|
||||
type: 'object',
|
||||
description: 'Company object with properties',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
123
apps/sim/tools/hubspot/get_contact.ts
Normal file
123
apps/sim/tools/hubspot/get_contact.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { HubSpotGetContactParams, HubSpotGetContactResponse } from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotGetContact')
|
||||
|
||||
export const hubspotGetContactTool: ToolConfig<HubSpotGetContactParams, HubSpotGetContactResponse> =
|
||||
{
|
||||
id: 'hubspot_get_contact',
|
||||
name: 'Get Contact from HubSpot',
|
||||
description: 'Retrieve a single contact by ID or email from HubSpot',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
contactId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID or email of the contact to retrieve',
|
||||
},
|
||||
idProperty: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Property to use as unique identifier (e.g., "email"). If not specified, uses record ID',
|
||||
},
|
||||
properties: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of properties to return',
|
||||
},
|
||||
associations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of object types to retrieve associated IDs for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.hubapi.com/crm/v3/objects/contacts/${params.contactId}`
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.idProperty) {
|
||||
queryParams.append('idProperty', params.idProperty)
|
||||
}
|
||||
if (params.properties) {
|
||||
queryParams.append('properties', params.properties)
|
||||
}
|
||||
if (params.associations) {
|
||||
queryParams.append('associations', params.associations)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to get contact from HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contact: data,
|
||||
metadata: {
|
||||
operation: 'get_contact' as const,
|
||||
contactId: data.id,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Contact data',
|
||||
properties: {
|
||||
contact: {
|
||||
type: 'object',
|
||||
description: 'Contact object with properties',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
99
apps/sim/tools/hubspot/get_users.ts
Normal file
99
apps/sim/tools/hubspot/get_users.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { HubSpotGetUsersParams, HubSpotGetUsersResponse } from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotGetUsers')
|
||||
|
||||
export const hubspotGetUsersTool: ToolConfig<HubSpotGetUsersParams, HubSpotGetUsersResponse> = {
|
||||
id: 'hubspot_get_users',
|
||||
name: 'Get Users from HubSpot',
|
||||
description: 'Retrieve all users from HubSpot account',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.hubapi.com/crm/v3/objects/users'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) {
|
||||
queryParams.append('limit', params.limit)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to fetch users from HubSpot')
|
||||
}
|
||||
|
||||
const users = data.results || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users,
|
||||
metadata: {
|
||||
operation: 'get_users' as const,
|
||||
totalItems: users.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Users data',
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
12
apps/sim/tools/hubspot/index.ts
Normal file
12
apps/sim/tools/hubspot/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { hubspotCreateCompanyTool } from './create_company'
|
||||
export { hubspotCreateContactTool } from './create_contact'
|
||||
export { hubspotGetCompanyTool } from './get_company'
|
||||
export { hubspotGetContactTool } from './get_contact'
|
||||
export { hubspotGetUsersTool } from './get_users'
|
||||
export { hubspotListCompaniesTool } from './list_companies'
|
||||
export { hubspotListContactsTool } from './list_contacts'
|
||||
export { hubspotListDealsTool } from './list_deals'
|
||||
export { hubspotSearchCompaniesTool } from './search_companies'
|
||||
export { hubspotSearchContactsTool } from './search_contacts'
|
||||
export { hubspotUpdateCompanyTool } from './update_company'
|
||||
export { hubspotUpdateContactTool } from './update_contact'
|
||||
136
apps/sim/tools/hubspot/list_companies.ts
Normal file
136
apps/sim/tools/hubspot/list_companies.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
HubSpotListCompaniesParams,
|
||||
HubSpotListCompaniesResponse,
|
||||
} from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotListCompanies')
|
||||
|
||||
export const hubspotListCompaniesTool: ToolConfig<
|
||||
HubSpotListCompaniesParams,
|
||||
HubSpotListCompaniesResponse
|
||||
> = {
|
||||
id: 'hubspot_list_companies',
|
||||
name: 'List Companies from HubSpot',
|
||||
description: 'Retrieve all companies from HubSpot account with pagination support',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of results per page (max 100, default 100)',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Pagination cursor for next page of results',
|
||||
},
|
||||
properties: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of properties to return',
|
||||
},
|
||||
associations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of object types to retrieve associated IDs for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.hubapi.com/crm/v3/objects/companies'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) {
|
||||
queryParams.append('limit', params.limit)
|
||||
}
|
||||
if (params.after) {
|
||||
queryParams.append('after', params.after)
|
||||
}
|
||||
if (params.properties) {
|
||||
queryParams.append('properties', params.properties)
|
||||
}
|
||||
if (params.associations) {
|
||||
queryParams.append('associations', params.associations)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to list companies from HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
companies: data.results || [],
|
||||
paging: data.paging,
|
||||
metadata: {
|
||||
operation: 'list_companies' as const,
|
||||
totalReturned: data.results?.length || 0,
|
||||
hasMore: !!data.paging?.next,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Companies data',
|
||||
properties: {
|
||||
companies: {
|
||||
type: 'array',
|
||||
description: 'Array of company objects',
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination information',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
134
apps/sim/tools/hubspot/list_contacts.ts
Normal file
134
apps/sim/tools/hubspot/list_contacts.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { HubSpotListContactsParams, HubSpotListContactsResponse } from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotListContacts')
|
||||
|
||||
export const hubspotListContactsTool: ToolConfig<
|
||||
HubSpotListContactsParams,
|
||||
HubSpotListContactsResponse
|
||||
> = {
|
||||
id: 'hubspot_list_contacts',
|
||||
name: 'List Contacts from HubSpot',
|
||||
description: 'Retrieve all contacts from HubSpot account with pagination support',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of results per page (max 100, default 100)',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Pagination cursor for next page of results',
|
||||
},
|
||||
properties: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Comma-separated list of properties to return (e.g., "email,firstname,lastname")',
|
||||
},
|
||||
associations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of object types to retrieve associated IDs for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.hubapi.com/crm/v3/objects/contacts'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) {
|
||||
queryParams.append('limit', params.limit)
|
||||
}
|
||||
if (params.after) {
|
||||
queryParams.append('after', params.after)
|
||||
}
|
||||
if (params.properties) {
|
||||
queryParams.append('properties', params.properties)
|
||||
}
|
||||
if (params.associations) {
|
||||
queryParams.append('associations', params.associations)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to list contacts from HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contacts: data.results || [],
|
||||
paging: data.paging,
|
||||
metadata: {
|
||||
operation: 'list_contacts' as const,
|
||||
totalReturned: data.results?.length || 0,
|
||||
hasMore: !!data.paging?.next,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Contacts data',
|
||||
properties: {
|
||||
contacts: {
|
||||
type: 'array',
|
||||
description: 'Array of contact objects',
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination information',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
130
apps/sim/tools/hubspot/list_deals.ts
Normal file
130
apps/sim/tools/hubspot/list_deals.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { HubSpotListDealsParams, HubSpotListDealsResponse } from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotListDeals')
|
||||
|
||||
export const hubspotListDealsTool: ToolConfig<HubSpotListDealsParams, HubSpotListDealsResponse> = {
|
||||
id: 'hubspot_list_deals',
|
||||
name: 'List Deals from HubSpot',
|
||||
description: 'Retrieve all deals from HubSpot account with pagination support',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of results per page (max 100, default 100)',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Pagination cursor for next page of results',
|
||||
},
|
||||
properties: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of properties to return',
|
||||
},
|
||||
associations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of object types to retrieve associated IDs for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.hubapi.com/crm/v3/objects/deals'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) {
|
||||
queryParams.append('limit', params.limit)
|
||||
}
|
||||
if (params.after) {
|
||||
queryParams.append('after', params.after)
|
||||
}
|
||||
if (params.properties) {
|
||||
queryParams.append('properties', params.properties)
|
||||
}
|
||||
if (params.associations) {
|
||||
queryParams.append('associations', params.associations)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to list deals from HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deals: data.results || [],
|
||||
paging: data.paging,
|
||||
metadata: {
|
||||
operation: 'list_deals' as const,
|
||||
totalReturned: data.results?.length || 0,
|
||||
hasMore: !!data.paging?.next,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Deals data',
|
||||
properties: {
|
||||
deals: {
|
||||
type: 'array',
|
||||
description: 'Array of deal objects',
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination information',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
160
apps/sim/tools/hubspot/search_companies.ts
Normal file
160
apps/sim/tools/hubspot/search_companies.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
HubSpotSearchCompaniesParams,
|
||||
HubSpotSearchCompaniesResponse,
|
||||
} from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotSearchCompanies')
|
||||
|
||||
export const hubspotSearchCompaniesTool: ToolConfig<
|
||||
HubSpotSearchCompaniesParams,
|
||||
HubSpotSearchCompaniesResponse
|
||||
> = {
|
||||
id: 'hubspot_search_companies',
|
||||
name: 'Search Companies in HubSpot',
|
||||
description: 'Search for companies in HubSpot using filters, sorting, and queries',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
filterGroups: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Array of filter groups. Each group contains filters with propertyName, operator, and value',
|
||||
},
|
||||
sorts: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Array of sort objects with propertyName and direction ("ASCENDING" or "DESCENDING")',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Search query string',
|
||||
},
|
||||
properties: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Array of property names to return',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of results to return (max 100)',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Pagination cursor for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.hubapi.com/crm/v3/objects/companies/search',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: any = {}
|
||||
|
||||
if (params.filterGroups && params.filterGroups.length > 0) {
|
||||
body.filterGroups = params.filterGroups
|
||||
}
|
||||
if (params.sorts && params.sorts.length > 0) {
|
||||
body.sorts = params.sorts
|
||||
}
|
||||
if (params.query) {
|
||||
body.query = params.query
|
||||
}
|
||||
if (params.properties && params.properties.length > 0) {
|
||||
body.properties = params.properties
|
||||
}
|
||||
if (params.limit) {
|
||||
body.limit = params.limit
|
||||
}
|
||||
if (params.after) {
|
||||
body.after = params.after
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to search companies in HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
companies: data.results || [],
|
||||
total: data.total,
|
||||
paging: data.paging,
|
||||
metadata: {
|
||||
operation: 'search_companies' as const,
|
||||
totalReturned: data.results?.length || 0,
|
||||
total: data.total,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Search results',
|
||||
properties: {
|
||||
companies: {
|
||||
type: 'array',
|
||||
description: 'Array of matching company objects',
|
||||
},
|
||||
total: {
|
||||
type: 'number',
|
||||
description: 'Total number of matching companies',
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination information',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
160
apps/sim/tools/hubspot/search_contacts.ts
Normal file
160
apps/sim/tools/hubspot/search_contacts.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
HubSpotSearchContactsParams,
|
||||
HubSpotSearchContactsResponse,
|
||||
} from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotSearchContacts')
|
||||
|
||||
export const hubspotSearchContactsTool: ToolConfig<
|
||||
HubSpotSearchContactsParams,
|
||||
HubSpotSearchContactsResponse
|
||||
> = {
|
||||
id: 'hubspot_search_contacts',
|
||||
name: 'Search Contacts in HubSpot',
|
||||
description: 'Search for contacts in HubSpot using filters, sorting, and queries',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
filterGroups: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Array of filter groups. Each group contains filters with propertyName, operator, and value',
|
||||
},
|
||||
sorts: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Array of sort objects with propertyName and direction ("ASCENDING" or "DESCENDING")',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Search query string',
|
||||
},
|
||||
properties: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Array of property names to return',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of results to return (max 100)',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Pagination cursor for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.hubapi.com/crm/v3/objects/contacts/search',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: any = {}
|
||||
|
||||
if (params.filterGroups && params.filterGroups.length > 0) {
|
||||
body.filterGroups = params.filterGroups
|
||||
}
|
||||
if (params.sorts && params.sorts.length > 0) {
|
||||
body.sorts = params.sorts
|
||||
}
|
||||
if (params.query) {
|
||||
body.query = params.query
|
||||
}
|
||||
if (params.properties && params.properties.length > 0) {
|
||||
body.properties = params.properties
|
||||
}
|
||||
if (params.limit) {
|
||||
body.limit = params.limit
|
||||
}
|
||||
if (params.after) {
|
||||
body.after = params.after
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to search contacts in HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contacts: data.results || [],
|
||||
total: data.total,
|
||||
paging: data.paging,
|
||||
metadata: {
|
||||
operation: 'search_contacts' as const,
|
||||
totalReturned: data.results?.length || 0,
|
||||
total: data.total,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Search results',
|
||||
properties: {
|
||||
contacts: {
|
||||
type: 'array',
|
||||
description: 'Array of matching contact objects',
|
||||
},
|
||||
total: {
|
||||
type: 'number',
|
||||
description: 'Total number of matching contacts',
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination information',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
258
apps/sim/tools/hubspot/types.ts
Normal file
258
apps/sim/tools/hubspot/types.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// Common HubSpot types
|
||||
export interface HubSpotUser {
|
||||
id: string
|
||||
email: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
roleId?: string
|
||||
primaryTeamId?: string
|
||||
superAdmin?: boolean
|
||||
}
|
||||
|
||||
export interface HubSpotContact {
|
||||
id: string
|
||||
properties: Record<string, any>
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
archived: boolean
|
||||
associations?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface HubSpotPaging {
|
||||
next?: {
|
||||
after: string
|
||||
link?: string
|
||||
}
|
||||
}
|
||||
|
||||
// Users
|
||||
export interface HubSpotGetUsersResponse extends ToolResponse {
|
||||
output: {
|
||||
users: HubSpotUser[]
|
||||
metadata: {
|
||||
operation: 'get_users'
|
||||
totalItems?: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface HubSpotGetUsersParams {
|
||||
accessToken: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
// List Contacts
|
||||
export interface HubSpotListContactsResponse extends ToolResponse {
|
||||
output: {
|
||||
contacts: HubSpotContact[]
|
||||
paging?: HubSpotPaging
|
||||
metadata: {
|
||||
operation: 'list_contacts'
|
||||
totalReturned: number
|
||||
hasMore: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface HubSpotListContactsParams {
|
||||
accessToken: string
|
||||
limit?: string
|
||||
after?: string
|
||||
properties?: string
|
||||
associations?: string
|
||||
}
|
||||
|
||||
// Get Contact
|
||||
export interface HubSpotGetContactResponse extends ToolResponse {
|
||||
output: {
|
||||
contact: HubSpotContact
|
||||
metadata: {
|
||||
operation: 'get_contact'
|
||||
contactId: string
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface HubSpotGetContactParams {
|
||||
accessToken: string
|
||||
contactId: string
|
||||
idProperty?: string
|
||||
properties?: string
|
||||
associations?: string
|
||||
}
|
||||
|
||||
// Create Contact
|
||||
export interface HubSpotCreateContactResponse extends ToolResponse {
|
||||
output: {
|
||||
contact: HubSpotContact
|
||||
metadata: {
|
||||
operation: 'create_contact'
|
||||
contactId: string
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface HubSpotCreateContactParams {
|
||||
accessToken: string
|
||||
properties: Record<string, any>
|
||||
associations?: Array<{
|
||||
to: { id: string }
|
||||
types: Array<{
|
||||
associationCategory: string
|
||||
associationTypeId: number
|
||||
}>
|
||||
}>
|
||||
}
|
||||
|
||||
// Update Contact
|
||||
export interface HubSpotUpdateContactResponse extends ToolResponse {
|
||||
output: {
|
||||
contact: HubSpotContact
|
||||
metadata: {
|
||||
operation: 'update_contact'
|
||||
contactId: string
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface HubSpotUpdateContactParams {
|
||||
accessToken: string
|
||||
contactId: string
|
||||
idProperty?: string
|
||||
properties: Record<string, any>
|
||||
}
|
||||
|
||||
// Search Contacts
|
||||
export interface HubSpotSearchContactsResponse extends ToolResponse {
|
||||
output: {
|
||||
contacts: HubSpotContact[]
|
||||
total: number
|
||||
paging?: HubSpotPaging
|
||||
metadata: {
|
||||
operation: 'search_contacts'
|
||||
totalReturned: number
|
||||
total: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface HubSpotSearchContactsParams {
|
||||
accessToken: string
|
||||
filterGroups?: Array<{
|
||||
filters: Array<{
|
||||
propertyName: string
|
||||
operator: string
|
||||
value: string
|
||||
}>
|
||||
}>
|
||||
sorts?: Array<{
|
||||
propertyName: string
|
||||
direction: 'ASCENDING' | 'DESCENDING'
|
||||
}>
|
||||
query?: string
|
||||
properties?: string[]
|
||||
limit?: number
|
||||
after?: string
|
||||
}
|
||||
|
||||
// Companies (same structure as contacts)
|
||||
export type HubSpotCompany = HubSpotContact
|
||||
export type HubSpotListCompaniesParams = HubSpotListContactsParams
|
||||
export type HubSpotListCompaniesResponse = Omit<HubSpotListContactsResponse, 'output'> & {
|
||||
output: {
|
||||
companies: HubSpotContact[]
|
||||
paging?: HubSpotPaging
|
||||
metadata: {
|
||||
operation: 'list_companies'
|
||||
totalReturned: number
|
||||
hasMore: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
export type HubSpotGetCompanyParams = HubSpotGetContactParams & { companyId: string }
|
||||
export type HubSpotGetCompanyResponse = Omit<HubSpotGetContactResponse, 'output'> & {
|
||||
output: {
|
||||
company: HubSpotContact
|
||||
metadata: {
|
||||
operation: 'get_company'
|
||||
companyId: string
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
export type HubSpotCreateCompanyParams = HubSpotCreateContactParams
|
||||
export type HubSpotCreateCompanyResponse = Omit<HubSpotCreateContactResponse, 'output'> & {
|
||||
output: {
|
||||
company: HubSpotContact
|
||||
metadata: {
|
||||
operation: 'create_company'
|
||||
companyId: string
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
export type HubSpotUpdateCompanyParams = HubSpotUpdateContactParams & { companyId: string }
|
||||
export type HubSpotUpdateCompanyResponse = Omit<HubSpotUpdateContactResponse, 'output'> & {
|
||||
output: {
|
||||
company: HubSpotContact
|
||||
metadata: {
|
||||
operation: 'update_company'
|
||||
companyId: string
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
export type HubSpotSearchCompaniesParams = HubSpotSearchContactsParams
|
||||
export type HubSpotSearchCompaniesResponse = Omit<HubSpotSearchContactsResponse, 'output'> & {
|
||||
output: {
|
||||
companies: HubSpotContact[]
|
||||
total: number
|
||||
paging?: HubSpotPaging
|
||||
metadata: {
|
||||
operation: 'search_companies'
|
||||
totalReturned: number
|
||||
total: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Deals (same structure as contacts)
|
||||
export type HubSpotDeal = HubSpotContact
|
||||
export type HubSpotListDealsParams = HubSpotListContactsParams
|
||||
export type HubSpotListDealsResponse = Omit<HubSpotListContactsResponse, 'output'> & {
|
||||
output: {
|
||||
deals: HubSpotContact[]
|
||||
paging?: HubSpotPaging
|
||||
metadata: {
|
||||
operation: 'list_deals'
|
||||
totalReturned: number
|
||||
hasMore: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Generic HubSpot response type for the block
|
||||
export type HubSpotResponse =
|
||||
| HubSpotGetUsersResponse
|
||||
| HubSpotListContactsResponse
|
||||
| HubSpotGetContactResponse
|
||||
| HubSpotCreateContactResponse
|
||||
| HubSpotUpdateContactResponse
|
||||
| HubSpotSearchContactsResponse
|
||||
| HubSpotListCompaniesResponse
|
||||
| HubSpotGetCompanyResponse
|
||||
| HubSpotCreateCompanyResponse
|
||||
| HubSpotUpdateCompanyResponse
|
||||
| HubSpotSearchCompaniesResponse
|
||||
| HubSpotListDealsResponse
|
||||
117
apps/sim/tools/hubspot/update_company.ts
Normal file
117
apps/sim/tools/hubspot/update_company.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
HubSpotUpdateCompanyParams,
|
||||
HubSpotUpdateCompanyResponse,
|
||||
} from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotUpdateCompany')
|
||||
|
||||
export const hubspotUpdateCompanyTool: ToolConfig<
|
||||
HubSpotUpdateCompanyParams,
|
||||
HubSpotUpdateCompanyResponse
|
||||
> = {
|
||||
id: 'hubspot_update_company',
|
||||
name: 'Update Company in HubSpot',
|
||||
description: 'Update an existing company in HubSpot by ID or domain',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
companyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID or domain of the company to update',
|
||||
},
|
||||
idProperty: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Property to use as unique identifier (e.g., "domain"). If not specified, uses record ID',
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Company properties to update as JSON object',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.hubapi.com/crm/v3/objects/companies/${params.companyId}`
|
||||
if (params.idProperty) {
|
||||
return `${baseUrl}?idProperty=${params.idProperty}`
|
||||
}
|
||||
return baseUrl
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
return {
|
||||
properties: params.properties,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to update company in HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
company: data,
|
||||
metadata: {
|
||||
operation: 'update_company' as const,
|
||||
companyId: data.id,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Updated company data',
|
||||
properties: {
|
||||
company: {
|
||||
type: 'object',
|
||||
description: 'Updated company object with properties',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
117
apps/sim/tools/hubspot/update_contact.ts
Normal file
117
apps/sim/tools/hubspot/update_contact.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
HubSpotUpdateContactParams,
|
||||
HubSpotUpdateContactResponse,
|
||||
} from '@/tools/hubspot/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('HubSpotUpdateContact')
|
||||
|
||||
export const hubspotUpdateContactTool: ToolConfig<
|
||||
HubSpotUpdateContactParams,
|
||||
HubSpotUpdateContactResponse
|
||||
> = {
|
||||
id: 'hubspot_update_contact',
|
||||
name: 'Update Contact in HubSpot',
|
||||
description: 'Update an existing contact in HubSpot by ID or email',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'hubspot',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the HubSpot API',
|
||||
},
|
||||
contactId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID or email of the contact to update',
|
||||
},
|
||||
idProperty: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Property to use as unique identifier (e.g., "email"). If not specified, uses record ID',
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Contact properties to update as JSON object',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.hubapi.com/crm/v3/objects/contacts/${params.contactId}`
|
||||
if (params.idProperty) {
|
||||
return `${baseUrl}?idProperty=${params.idProperty}`
|
||||
}
|
||||
return baseUrl
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
return {
|
||||
properties: params.properties,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('HubSpot API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to update contact in HubSpot')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contact: data,
|
||||
metadata: {
|
||||
operation: 'update_contact' as const,
|
||||
contactId: data.id,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Updated contact data',
|
||||
properties: {
|
||||
contact: {
|
||||
type: 'object',
|
||||
description: 'Updated contact object with properties',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -193,6 +193,9 @@ export async function executeTool(
|
||||
|
||||
const data = await response.json()
|
||||
contextParams.accessToken = data.accessToken
|
||||
if (data.idToken) {
|
||||
contextParams.idToken = data.idToken
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Successfully got access token for ${toolId}, length: ${data.accessToken?.length || 0}`
|
||||
|
||||
152
apps/sim/tools/pipedrive/create_activity.ts
Normal file
152
apps/sim/tools/pipedrive/create_activity.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveCreateActivityParams,
|
||||
PipedriveCreateActivityResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveCreateActivity')
|
||||
|
||||
export const pipedriveCreateActivityTool: ToolConfig<
|
||||
PipedriveCreateActivityParams,
|
||||
PipedriveCreateActivityResponse
|
||||
> = {
|
||||
id: 'pipedrive_create_activity',
|
||||
name: 'Create Activity in Pipedrive',
|
||||
description: 'Create a new activity (task) in Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The subject/title of the activity',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Activity type: call, meeting, task, deadline, email, lunch',
|
||||
},
|
||||
due_date: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Due date in YYYY-MM-DD format',
|
||||
},
|
||||
due_time: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Due time in HH:MM format',
|
||||
},
|
||||
duration: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Duration in HH:MM format',
|
||||
},
|
||||
deal_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the deal to associate with',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the person to associate with',
|
||||
},
|
||||
org_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the organization to associate with',
|
||||
},
|
||||
note: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Notes for the activity',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.pipedrive.com/v1/activities',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
subject: params.subject,
|
||||
type: params.type,
|
||||
due_date: params.due_date,
|
||||
}
|
||||
|
||||
if (params.due_time) body.due_time = params.due_time
|
||||
if (params.duration) body.duration = params.duration
|
||||
if (params.deal_id) body.deal_id = Number(params.deal_id)
|
||||
if (params.person_id) body.person_id = Number(params.person_id)
|
||||
if (params.org_id) body.org_id = Number(params.org_id)
|
||||
if (params.note) body.note = params.note
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to create activity in Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
activity: data.data,
|
||||
metadata: {
|
||||
operation: 'create_activity' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created activity details',
|
||||
properties: {
|
||||
activity: {
|
||||
type: 'object',
|
||||
description: 'The created activity object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
152
apps/sim/tools/pipedrive/create_deal.ts
Normal file
152
apps/sim/tools/pipedrive/create_deal.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveCreateDealParams,
|
||||
PipedriveCreateDealResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveCreateDeal')
|
||||
|
||||
export const pipedriveCreateDealTool: ToolConfig<
|
||||
PipedriveCreateDealParams,
|
||||
PipedriveCreateDealResponse
|
||||
> = {
|
||||
id: 'pipedrive_create_deal',
|
||||
name: 'Create Deal in Pipedrive',
|
||||
description: 'Create a new deal in Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The title of the deal',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'The monetary value of the deal',
|
||||
},
|
||||
currency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Currency code (e.g., USD, EUR)',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the person this deal is associated with',
|
||||
},
|
||||
org_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the organization this deal is associated with',
|
||||
},
|
||||
pipeline_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the pipeline this deal should be placed in',
|
||||
},
|
||||
stage_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the stage this deal should be placed in',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Status of the deal: open, won, lost',
|
||||
},
|
||||
expected_close_date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Expected close date in YYYY-MM-DD format',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.pipedrive.com/api/v2/deals',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
title: params.title,
|
||||
}
|
||||
|
||||
if (params.value) body.value = Number(params.value)
|
||||
if (params.currency) body.currency = params.currency
|
||||
if (params.person_id) body.person_id = Number(params.person_id)
|
||||
if (params.org_id) body.org_id = Number(params.org_id)
|
||||
if (params.pipeline_id) body.pipeline_id = Number(params.pipeline_id)
|
||||
if (params.stage_id) body.stage_id = Number(params.stage_id)
|
||||
if (params.status) body.status = params.status
|
||||
if (params.expected_close_date) body.expected_close_date = params.expected_close_date
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to create deal in Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deal: data.data,
|
||||
metadata: {
|
||||
operation: 'create_deal' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created deal details',
|
||||
properties: {
|
||||
deal: {
|
||||
type: 'object',
|
||||
description: 'The created deal object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
161
apps/sim/tools/pipedrive/create_lead.ts
Normal file
161
apps/sim/tools/pipedrive/create_lead.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveCreateLeadParams,
|
||||
PipedriveCreateLeadResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveCreateLead')
|
||||
|
||||
export const pipedriveCreateLeadTool: ToolConfig<
|
||||
PipedriveCreateLeadParams,
|
||||
PipedriveCreateLeadResponse
|
||||
> = {
|
||||
id: 'pipedrive_create_lead',
|
||||
name: 'Create Lead in Pipedrive',
|
||||
description: 'Create a new lead in Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'pipedrive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The name of the lead',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the person (REQUIRED unless organization_id is provided)',
|
||||
},
|
||||
organization_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the organization (REQUIRED unless person_id is provided)',
|
||||
},
|
||||
owner_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the user who will own the lead',
|
||||
},
|
||||
value_amount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Potential value amount',
|
||||
},
|
||||
value_currency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Currency code (e.g., USD, EUR)',
|
||||
},
|
||||
expected_close_date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Expected close date in YYYY-MM-DD format',
|
||||
},
|
||||
visible_to: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Visibility: 1 (Owner & followers), 3 (Entire company)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.pipedrive.com/v1/leads',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
if (!params.person_id && !params.organization_id) {
|
||||
throw new Error('Either person_id or organization_id is required to create a lead')
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
title: params.title,
|
||||
}
|
||||
|
||||
if (params.person_id) body.person_id = Number(params.person_id)
|
||||
if (params.organization_id) body.organization_id = Number(params.organization_id)
|
||||
if (params.owner_id) body.owner_id = Number(params.owner_id)
|
||||
|
||||
// Build value object if both amount and currency are provided
|
||||
if (params.value_amount && params.value_currency) {
|
||||
body.value = {
|
||||
amount: Number(params.value_amount),
|
||||
currency: params.value_currency,
|
||||
}
|
||||
}
|
||||
|
||||
if (params.expected_close_date) body.expected_close_date = params.expected_close_date
|
||||
if (params.visible_to) body.visible_to = Number(params.visible_to)
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to create lead in Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
lead: data.data,
|
||||
metadata: {
|
||||
operation: 'create_lead' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created lead details',
|
||||
properties: {
|
||||
lead: {
|
||||
type: 'object',
|
||||
description: 'The created lead object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
117
apps/sim/tools/pipedrive/create_project.ts
Normal file
117
apps/sim/tools/pipedrive/create_project.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveCreateProjectParams,
|
||||
PipedriveCreateProjectResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveCreateProject')
|
||||
|
||||
export const pipedriveCreateProjectTool: ToolConfig<
|
||||
PipedriveCreateProjectParams,
|
||||
PipedriveCreateProjectResponse
|
||||
> = {
|
||||
id: 'pipedrive_create_project',
|
||||
name: 'Create Project in Pipedrive',
|
||||
description: 'Create a new project in Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The title of the project',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description of the project',
|
||||
},
|
||||
start_date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Project start date in YYYY-MM-DD format',
|
||||
},
|
||||
end_date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Project end date in YYYY-MM-DD format',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.pipedrive.com/v1/projects',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
title: params.title,
|
||||
}
|
||||
|
||||
if (params.description) body.description = params.description
|
||||
if (params.start_date) body.start_date = params.start_date
|
||||
if (params.end_date) body.end_date = params.end_date
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to create project in Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
project: data.data,
|
||||
metadata: {
|
||||
operation: 'create_project' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created project details',
|
||||
properties: {
|
||||
project: {
|
||||
type: 'object',
|
||||
description: 'The created project object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
92
apps/sim/tools/pipedrive/delete_lead.ts
Normal file
92
apps/sim/tools/pipedrive/delete_lead.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveDeleteLeadParams,
|
||||
PipedriveDeleteLeadResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveDeleteLead')
|
||||
|
||||
export const pipedriveDeleteLeadTool: ToolConfig<
|
||||
PipedriveDeleteLeadParams,
|
||||
PipedriveDeleteLeadResponse
|
||||
> = {
|
||||
id: 'pipedrive_delete_lead',
|
||||
name: 'Delete Lead from Pipedrive',
|
||||
description: 'Delete a specific lead from Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'pipedrive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
lead_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the lead to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.pipedrive.com/v1/leads/${params.lead_id}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to delete lead from Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
data: data.data,
|
||||
metadata: {
|
||||
operation: 'delete_lead' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Deletion result',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
description: 'Deletion confirmation data',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
133
apps/sim/tools/pipedrive/get_activities.ts
Normal file
133
apps/sim/tools/pipedrive/get_activities.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveGetActivitiesParams,
|
||||
PipedriveGetActivitiesResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetActivities')
|
||||
|
||||
export const pipedriveGetActivitiesTool: ToolConfig<
|
||||
PipedriveGetActivitiesParams,
|
||||
PipedriveGetActivitiesResponse
|
||||
> = {
|
||||
id: 'pipedrive_get_activities',
|
||||
name: 'Get Activities from Pipedrive',
|
||||
description: 'Retrieve activities (tasks) from Pipedrive with optional filters',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
deal_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter activities by deal ID',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter activities by person ID',
|
||||
},
|
||||
org_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter activities by organization ID',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by activity type (call, meeting, task, deadline, email, lunch)',
|
||||
},
|
||||
done: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by completion status: 0 for not done, 1 for done',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 500)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.pipedrive.com/v1/activities'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.deal_id) queryParams.append('deal_id', params.deal_id)
|
||||
if (params.person_id) queryParams.append('person_id', params.person_id)
|
||||
if (params.org_id) queryParams.append('org_id', params.org_id)
|
||||
if (params.type) queryParams.append('type', params.type)
|
||||
if (params.done) queryParams.append('done', params.done)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch activities from Pipedrive')
|
||||
}
|
||||
|
||||
const activities = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
activities,
|
||||
metadata: {
|
||||
operation: 'get_activities' as const,
|
||||
totalItems: activities.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Activities data',
|
||||
properties: {
|
||||
activities: {
|
||||
type: 'array',
|
||||
description: 'Array of activity objects from Pipedrive',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
161
apps/sim/tools/pipedrive/get_all_deals.ts
Normal file
161
apps/sim/tools/pipedrive/get_all_deals.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveGetAllDealsParams,
|
||||
PipedriveGetAllDealsResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetAllDeals')
|
||||
|
||||
export const pipedriveGetAllDealsTool: ToolConfig<
|
||||
PipedriveGetAllDealsParams,
|
||||
PipedriveGetAllDealsResponse
|
||||
> = {
|
||||
id: 'pipedrive_get_all_deals',
|
||||
name: 'Get All Deals from Pipedrive',
|
||||
description: 'Retrieve all deals from Pipedrive with optional filters',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Only fetch deals with a specific status. Values: open, won, lost. If omitted, all not deleted deals are returned',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'If supplied, only deals linked to the specified person are returned',
|
||||
},
|
||||
org_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'If supplied, only deals linked to the specified organization are returned',
|
||||
},
|
||||
pipeline_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'If supplied, only deals in the specified pipeline are returned',
|
||||
},
|
||||
updated_since: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'If set, only deals updated after this time are returned. Format: 2025-01-01T10:20:00Z',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 500)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.pipedrive.com/api/v2/deals'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
// Add optional parameters to query string if they exist
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.person_id) queryParams.append('person_id', params.person_id)
|
||||
if (params.org_id) queryParams.append('org_id', params.org_id)
|
||||
if (params.pipeline_id) queryParams.append('pipeline_id', params.pipeline_id)
|
||||
if (params.updated_since) queryParams.append('updated_since', params.updated_since)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: PipedriveGetAllDealsParams) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch deals from Pipedrive')
|
||||
}
|
||||
|
||||
const deals = data.data || []
|
||||
const hasMore = data.additional_data?.pagination?.more_items_in_collection || false
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deals,
|
||||
metadata: {
|
||||
operation: 'get_all_deals' as const,
|
||||
totalItems: deals.length,
|
||||
hasMore,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Deals data and metadata',
|
||||
properties: {
|
||||
deals: {
|
||||
type: 'array',
|
||||
description: 'Array of deal objects from Pipedrive',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Deal ID' },
|
||||
title: { type: 'string', description: 'Deal title' },
|
||||
value: { type: 'number', description: 'Deal value' },
|
||||
currency: { type: 'string', description: 'Deal currency' },
|
||||
status: { type: 'string', description: 'Deal status' },
|
||||
stage_id: { type: 'number', description: 'Stage ID' },
|
||||
pipeline_id: { type: 'number', description: 'Pipeline ID' },
|
||||
owner_id: { type: 'number', description: 'Owner user ID' },
|
||||
add_time: { type: 'string', description: 'Deal creation time' },
|
||||
update_time: { type: 'string', description: 'Deal last update time' },
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
properties: {
|
||||
operation: { type: 'string', description: 'The operation performed' },
|
||||
totalItems: { type: 'number', description: 'Total number of deals returned' },
|
||||
hasMore: {
|
||||
type: 'boolean',
|
||||
description: 'Whether there are more items to fetch via pagination',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
81
apps/sim/tools/pipedrive/get_deal.ts
Normal file
81
apps/sim/tools/pipedrive/get_deal.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { PipedriveGetDealParams, PipedriveGetDealResponse } from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetDeal')
|
||||
|
||||
export const pipedriveGetDealTool: ToolConfig<PipedriveGetDealParams, PipedriveGetDealResponse> = {
|
||||
id: 'pipedrive_get_deal',
|
||||
name: 'Get Deal Details from Pipedrive',
|
||||
description: 'Retrieve detailed information about a specific deal',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
deal_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the deal to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.pipedrive.com/api/v2/deals/${params.deal_id}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch deal from Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deal: data.data,
|
||||
metadata: {
|
||||
operation: 'get_deal' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Deal details',
|
||||
properties: {
|
||||
deal: {
|
||||
type: 'object',
|
||||
description: 'Deal object with full details',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
114
apps/sim/tools/pipedrive/get_files.ts
Normal file
114
apps/sim/tools/pipedrive/get_files.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { PipedriveGetFilesParams, PipedriveGetFilesResponse } from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetFiles')
|
||||
|
||||
export const pipedriveGetFilesTool: ToolConfig<PipedriveGetFilesParams, PipedriveGetFilesResponse> =
|
||||
{
|
||||
id: 'pipedrive_get_files',
|
||||
name: 'Get Files from Pipedrive',
|
||||
description: 'Retrieve files from Pipedrive with optional filters',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
deal_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter files by deal ID',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter files by person ID',
|
||||
},
|
||||
org_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter files by organization ID',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 500)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.pipedrive.com/v1/files'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.deal_id) queryParams.append('deal_id', params.deal_id)
|
||||
if (params.person_id) queryParams.append('person_id', params.person_id)
|
||||
if (params.org_id) queryParams.append('org_id', params.org_id)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch files from Pipedrive')
|
||||
}
|
||||
|
||||
const files = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
files,
|
||||
metadata: {
|
||||
operation: 'get_files' as const,
|
||||
totalItems: files.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Files data',
|
||||
properties: {
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Array of file objects from Pipedrive',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
160
apps/sim/tools/pipedrive/get_leads.ts
Normal file
160
apps/sim/tools/pipedrive/get_leads.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { PipedriveGetLeadsParams, PipedriveGetLeadsResponse } from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetLeads')
|
||||
|
||||
export const pipedriveGetLeadsTool: ToolConfig<PipedriveGetLeadsParams, PipedriveGetLeadsResponse> =
|
||||
{
|
||||
id: 'pipedrive_get_leads',
|
||||
name: 'Get Leads from Pipedrive',
|
||||
description: 'Retrieve all leads or a specific lead from Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'pipedrive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
lead_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Optional: ID of a specific lead to retrieve',
|
||||
},
|
||||
archived: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Get archived leads instead of active ones',
|
||||
},
|
||||
owner_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by owner user ID',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by person ID',
|
||||
},
|
||||
organization_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by organization ID',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 500)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
// If lead_id is provided, get specific lead
|
||||
if (params.lead_id) {
|
||||
return `https://api.pipedrive.com/v1/leads/${params.lead_id}`
|
||||
}
|
||||
|
||||
// Get archived or active leads with optional filters
|
||||
const baseUrl =
|
||||
params.archived === 'true'
|
||||
? 'https://api.pipedrive.com/v1/leads/archived'
|
||||
: 'https://api.pipedrive.com/v1/leads'
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.owner_id) queryParams.append('owner_id', params.owner_id)
|
||||
if (params.person_id) queryParams.append('person_id', params.person_id)
|
||||
if (params.organization_id) queryParams.append('organization_id', params.organization_id)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch lead(s) from Pipedrive')
|
||||
}
|
||||
|
||||
// If lead_id was provided, return single lead
|
||||
if (params?.lead_id) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
lead: data.data,
|
||||
metadata: {
|
||||
operation: 'get_leads' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, return list of leads
|
||||
const leads = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
leads,
|
||||
metadata: {
|
||||
operation: 'get_leads' as const,
|
||||
totalItems: leads.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Leads data or single lead details',
|
||||
properties: {
|
||||
leads: {
|
||||
type: 'array',
|
||||
description: 'Array of lead objects (when listing all)',
|
||||
},
|
||||
lead: {
|
||||
type: 'object',
|
||||
description: 'Single lead object (when lead_id is provided)',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
110
apps/sim/tools/pipedrive/get_mail_messages.ts
Normal file
110
apps/sim/tools/pipedrive/get_mail_messages.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveGetMailMessagesParams,
|
||||
PipedriveGetMailMessagesResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetMailMessages')
|
||||
|
||||
export const pipedriveGetMailMessagesTool: ToolConfig<
|
||||
PipedriveGetMailMessagesParams,
|
||||
PipedriveGetMailMessagesResponse
|
||||
> = {
|
||||
id: 'pipedrive_get_mail_messages',
|
||||
name: 'Get Mail Threads from Pipedrive',
|
||||
description: 'Retrieve mail threads from Pipedrive mailbox',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'pipedrive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
folder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by folder: inbox, drafts, sent, archive (default: inbox)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 50)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.pipedrive.com/v1/mailbox/mailThreads'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.folder) queryParams.append('folder', params.folder)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch mail threads from Pipedrive')
|
||||
}
|
||||
|
||||
const threads = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
messages: threads,
|
||||
metadata: {
|
||||
operation: 'get_mail_messages' as const,
|
||||
totalItems: threads.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Mail threads data',
|
||||
properties: {
|
||||
messages: {
|
||||
type: 'array',
|
||||
description: 'Array of mail thread objects from Pipedrive mailbox',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
97
apps/sim/tools/pipedrive/get_mail_thread.ts
Normal file
97
apps/sim/tools/pipedrive/get_mail_thread.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveGetMailThreadParams,
|
||||
PipedriveGetMailThreadResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetMailThread')
|
||||
|
||||
export const pipedriveGetMailThreadTool: ToolConfig<
|
||||
PipedriveGetMailThreadParams,
|
||||
PipedriveGetMailThreadResponse
|
||||
> = {
|
||||
id: 'pipedrive_get_mail_thread',
|
||||
name: 'Get Mail Thread Messages from Pipedrive',
|
||||
description: 'Retrieve all messages from a specific mail thread',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'pipedrive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
thread_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the mail thread',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.pipedrive.com/v1/mailbox/mailThreads/${params.thread_id}/mailMessages`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch mail thread from Pipedrive')
|
||||
}
|
||||
|
||||
const messages = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
messages,
|
||||
metadata: {
|
||||
operation: 'get_mail_thread' as const,
|
||||
threadId: params?.thread_id || '',
|
||||
totalItems: messages.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Mail thread messages data',
|
||||
properties: {
|
||||
messages: {
|
||||
type: 'array',
|
||||
description: 'Array of mail message objects from the thread',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata including thread ID',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
119
apps/sim/tools/pipedrive/get_pipeline_deals.ts
Normal file
119
apps/sim/tools/pipedrive/get_pipeline_deals.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveGetPipelineDealsParams,
|
||||
PipedriveGetPipelineDealsResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetPipelineDeals')
|
||||
|
||||
export const pipedriveGetPipelineDealsTool: ToolConfig<
|
||||
PipedriveGetPipelineDealsParams,
|
||||
PipedriveGetPipelineDealsResponse
|
||||
> = {
|
||||
id: 'pipedrive_get_pipeline_deals',
|
||||
name: 'Get Pipeline Deals from Pipedrive',
|
||||
description: 'Retrieve all deals in a specific pipeline',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
pipeline_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the pipeline',
|
||||
},
|
||||
stage_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by specific stage within the pipeline',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by deal status: open, won, lost',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 500)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.pipedrive.com/v1/pipelines/${params.pipeline_id}/deals`
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.stage_id) queryParams.append('stage_id', params.stage_id)
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch pipeline deals from Pipedrive')
|
||||
}
|
||||
|
||||
const deals = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deals,
|
||||
metadata: {
|
||||
operation: 'get_pipeline_deals' as const,
|
||||
pipelineId: params?.pipeline_id || '',
|
||||
totalItems: deals.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Pipeline deals data',
|
||||
properties: {
|
||||
deals: {
|
||||
type: 'array',
|
||||
description: 'Array of deal objects from the pipeline',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata including pipeline ID',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
119
apps/sim/tools/pipedrive/get_pipelines.ts
Normal file
119
apps/sim/tools/pipedrive/get_pipelines.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveGetPipelinesParams,
|
||||
PipedriveGetPipelinesResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetPipelines')
|
||||
|
||||
export const pipedriveGetPipelinesTool: ToolConfig<
|
||||
PipedriveGetPipelinesParams,
|
||||
PipedriveGetPipelinesResponse
|
||||
> = {
|
||||
id: 'pipedrive_get_pipelines',
|
||||
name: 'Get Pipelines from Pipedrive',
|
||||
description: 'Retrieve all pipelines from Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
sort_by: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Field to sort by: id, update_time, add_time (default: id)',
|
||||
},
|
||||
sort_direction: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Sorting direction: asc, desc (default: asc)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 500)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'For pagination, the marker representing the first item on the next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = 'https://api.pipedrive.com/v1/pipelines'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.sort_by) queryParams.append('sort_by', params.sort_by)
|
||||
if (params.sort_direction) queryParams.append('sort_direction', params.sort_direction)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch pipelines from Pipedrive')
|
||||
}
|
||||
|
||||
const pipelines = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
pipelines,
|
||||
metadata: {
|
||||
operation: 'get_pipelines' as const,
|
||||
totalItems: pipelines.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Pipelines data',
|
||||
properties: {
|
||||
pipelines: {
|
||||
type: 'array',
|
||||
description: 'Array of pipeline objects from Pipedrive',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
136
apps/sim/tools/pipedrive/get_projects.ts
Normal file
136
apps/sim/tools/pipedrive/get_projects.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveGetProjectsParams,
|
||||
PipedriveGetProjectsResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveGetProjects')
|
||||
|
||||
export const pipedriveGetProjectsTool: ToolConfig<
|
||||
PipedriveGetProjectsParams,
|
||||
PipedriveGetProjectsResponse
|
||||
> = {
|
||||
id: 'pipedrive_get_projects',
|
||||
name: 'Get Projects from Pipedrive',
|
||||
description: 'Retrieve all projects or a specific project from Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
project_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Optional: ID of a specific project to retrieve',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by project status: open, completed, deleted (only for listing all)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 500, only for listing all)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
// If project_id is provided, get specific project
|
||||
if (params.project_id) {
|
||||
return `https://api.pipedrive.com/v1/projects/${params.project_id}`
|
||||
}
|
||||
|
||||
// Otherwise, get all projects with optional filters
|
||||
const baseUrl = 'https://api.pipedrive.com/v1/projects'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to fetch project(s) from Pipedrive')
|
||||
}
|
||||
|
||||
// If project_id was provided, return single project
|
||||
if (params?.project_id) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
project: data.data,
|
||||
metadata: {
|
||||
operation: 'get_projects' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, return list of projects
|
||||
const projects = data.data || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
projects,
|
||||
metadata: {
|
||||
operation: 'get_projects' as const,
|
||||
totalItems: projects.length,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Projects data or single project details',
|
||||
properties: {
|
||||
projects: {
|
||||
type: 'array',
|
||||
description: 'Array of project objects (when listing all)',
|
||||
},
|
||||
project: {
|
||||
type: 'object',
|
||||
description: 'Single project object (when project_id is provided)',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
26
apps/sim/tools/pipedrive/index.ts
Normal file
26
apps/sim/tools/pipedrive/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
// Deal operations
|
||||
|
||||
export { pipedriveCreateActivityTool } from '@/tools/pipedrive/create_activity'
|
||||
export { pipedriveCreateDealTool } from '@/tools/pipedrive/create_deal'
|
||||
export { pipedriveCreateLeadTool } from '@/tools/pipedrive/create_lead'
|
||||
export { pipedriveCreateProjectTool } from '@/tools/pipedrive/create_project'
|
||||
export { pipedriveDeleteLeadTool } from '@/tools/pipedrive/delete_lead'
|
||||
// Activity operations
|
||||
export { pipedriveGetActivitiesTool } from '@/tools/pipedrive/get_activities'
|
||||
export { pipedriveGetAllDealsTool } from '@/tools/pipedrive/get_all_deals'
|
||||
export { pipedriveGetDealTool } from '@/tools/pipedrive/get_deal'
|
||||
// File operations
|
||||
export { pipedriveGetFilesTool } from '@/tools/pipedrive/get_files'
|
||||
// Lead operations
|
||||
export { pipedriveGetLeadsTool } from '@/tools/pipedrive/get_leads'
|
||||
// Mail operations
|
||||
export { pipedriveGetMailMessagesTool } from '@/tools/pipedrive/get_mail_messages'
|
||||
export { pipedriveGetMailThreadTool } from '@/tools/pipedrive/get_mail_thread'
|
||||
export { pipedriveGetPipelineDealsTool } from '@/tools/pipedrive/get_pipeline_deals'
|
||||
// Pipeline operations
|
||||
export { pipedriveGetPipelinesTool } from '@/tools/pipedrive/get_pipelines'
|
||||
// Project operations
|
||||
export { pipedriveGetProjectsTool } from '@/tools/pipedrive/get_projects'
|
||||
export { pipedriveUpdateActivityTool } from '@/tools/pipedrive/update_activity'
|
||||
export { pipedriveUpdateDealTool } from '@/tools/pipedrive/update_deal'
|
||||
export { pipedriveUpdateLeadTool } from '@/tools/pipedrive/update_lead'
|
||||
536
apps/sim/tools/pipedrive/types.ts
Normal file
536
apps/sim/tools/pipedrive/types.ts
Normal file
@@ -0,0 +1,536 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// Common Pipedrive types
|
||||
export interface PipedriveLead {
|
||||
id: string
|
||||
title: string
|
||||
person_id?: number
|
||||
organization_id?: number
|
||||
owner_id: number
|
||||
value?: {
|
||||
amount: number
|
||||
currency: string
|
||||
}
|
||||
expected_close_date?: string
|
||||
is_archived: boolean
|
||||
was_seen: boolean
|
||||
add_time: string
|
||||
update_time: string
|
||||
}
|
||||
|
||||
export interface PipedriveDeal {
|
||||
id: number
|
||||
title: string
|
||||
value: number
|
||||
currency: string
|
||||
status: string
|
||||
stage_id: number
|
||||
pipeline_id: number
|
||||
person_id?: number
|
||||
org_id?: number
|
||||
owner_id: number
|
||||
add_time: string
|
||||
update_time: string
|
||||
won_time?: string
|
||||
lost_time?: string
|
||||
close_time?: string
|
||||
expected_close_date?: string
|
||||
}
|
||||
|
||||
export interface PipedriveActivity {
|
||||
id: number
|
||||
subject: string
|
||||
type: string
|
||||
due_date: string
|
||||
due_time: string
|
||||
duration: string
|
||||
deal_id?: number
|
||||
person_id?: number
|
||||
org_id?: number
|
||||
done: boolean
|
||||
note: string
|
||||
add_time: string
|
||||
update_time: string
|
||||
}
|
||||
|
||||
export interface PipedriveFile {
|
||||
id: number
|
||||
name: string
|
||||
file_type: string
|
||||
file_size: number
|
||||
add_time: string
|
||||
update_time: string
|
||||
deal_id?: number
|
||||
person_id?: number
|
||||
org_id?: number
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface PipedrivePipeline {
|
||||
id: number
|
||||
name: string
|
||||
url_title: string
|
||||
order_nr: number
|
||||
active: boolean
|
||||
deal_probability: boolean
|
||||
add_time: string
|
||||
update_time: string
|
||||
}
|
||||
|
||||
export interface PipedriveProject {
|
||||
id: number
|
||||
title: string
|
||||
description?: string
|
||||
status: string
|
||||
owner_id: number
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
add_time: string
|
||||
update_time: string
|
||||
}
|
||||
|
||||
export interface PipedriveMailMessage {
|
||||
id: number
|
||||
subject: string
|
||||
snippet: string
|
||||
mail_thread_id: number
|
||||
from_address: string
|
||||
to_addresses: string[]
|
||||
cc_addresses?: string[]
|
||||
bcc_addresses?: string[]
|
||||
timestamp: string
|
||||
item_type: string
|
||||
deal_id?: number
|
||||
person_id?: number
|
||||
org_id?: number
|
||||
}
|
||||
|
||||
// GET All Deals
|
||||
export interface PipedriveGetAllDealsParams {
|
||||
accessToken: string
|
||||
status?: string
|
||||
person_id?: string
|
||||
org_id?: string
|
||||
pipeline_id?: string
|
||||
updated_since?: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetAllDealsOutput {
|
||||
deals: PipedriveDeal[]
|
||||
metadata: {
|
||||
operation: 'get_all_deals'
|
||||
totalItems: number
|
||||
hasMore: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetAllDealsResponse extends ToolResponse {
|
||||
output: PipedriveGetAllDealsOutput
|
||||
}
|
||||
|
||||
// GET Deal
|
||||
export interface PipedriveGetDealParams {
|
||||
accessToken: string
|
||||
deal_id: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetDealOutput {
|
||||
deal: PipedriveDeal
|
||||
metadata: {
|
||||
operation: 'get_deal'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetDealResponse extends ToolResponse {
|
||||
output: PipedriveGetDealOutput
|
||||
}
|
||||
|
||||
// CREATE Deal
|
||||
export interface PipedriveCreateDealParams {
|
||||
accessToken: string
|
||||
title: string
|
||||
value?: string
|
||||
currency?: string
|
||||
person_id?: string
|
||||
org_id?: string
|
||||
pipeline_id?: string
|
||||
stage_id?: string
|
||||
status?: string
|
||||
expected_close_date?: string
|
||||
}
|
||||
|
||||
export interface PipedriveCreateDealOutput {
|
||||
deal: PipedriveDeal
|
||||
metadata: {
|
||||
operation: 'create_deal'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveCreateDealResponse extends ToolResponse {
|
||||
output: PipedriveCreateDealOutput
|
||||
}
|
||||
|
||||
// UPDATE Deal
|
||||
export interface PipedriveUpdateDealParams {
|
||||
accessToken: string
|
||||
deal_id: string
|
||||
title?: string
|
||||
value?: string
|
||||
status?: string
|
||||
stage_id?: string
|
||||
expected_close_date?: string
|
||||
}
|
||||
|
||||
export interface PipedriveUpdateDealOutput {
|
||||
deal: PipedriveDeal
|
||||
metadata: {
|
||||
operation: 'update_deal'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveUpdateDealResponse extends ToolResponse {
|
||||
output: PipedriveUpdateDealOutput
|
||||
}
|
||||
|
||||
// GET Files
|
||||
export interface PipedriveGetFilesParams {
|
||||
accessToken: string
|
||||
deal_id?: string
|
||||
person_id?: string
|
||||
org_id?: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetFilesOutput {
|
||||
files: PipedriveFile[]
|
||||
metadata: {
|
||||
operation: 'get_files'
|
||||
totalItems: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetFilesResponse extends ToolResponse {
|
||||
output: PipedriveGetFilesOutput
|
||||
}
|
||||
|
||||
export interface PipedriveGetMailMessagesParams {
|
||||
accessToken: string
|
||||
folder?: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetMailMessagesOutput {
|
||||
messages: PipedriveMailMessage[]
|
||||
metadata: {
|
||||
operation: 'get_mail_messages'
|
||||
totalItems: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetMailMessagesResponse extends ToolResponse {
|
||||
output: PipedriveGetMailMessagesOutput
|
||||
}
|
||||
|
||||
// GET Mail Thread
|
||||
export interface PipedriveGetMailThreadParams {
|
||||
accessToken: string
|
||||
thread_id: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetMailThreadOutput {
|
||||
messages: PipedriveMailMessage[]
|
||||
metadata: {
|
||||
operation: 'get_mail_thread'
|
||||
threadId: string
|
||||
totalItems: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetMailThreadResponse extends ToolResponse {
|
||||
output: PipedriveGetMailThreadOutput
|
||||
}
|
||||
|
||||
// GET All Pipelines
|
||||
export interface PipedriveGetPipelinesParams {
|
||||
accessToken: string
|
||||
sort_by?: string
|
||||
sort_direction?: string
|
||||
limit?: string
|
||||
cursor?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetPipelinesOutput {
|
||||
pipelines: PipedrivePipeline[]
|
||||
metadata: {
|
||||
operation: 'get_pipelines'
|
||||
totalItems: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetPipelinesResponse extends ToolResponse {
|
||||
output: PipedriveGetPipelinesOutput
|
||||
}
|
||||
|
||||
// GET Pipeline Deals
|
||||
export interface PipedriveGetPipelineDealsParams {
|
||||
accessToken: string
|
||||
pipeline_id: string
|
||||
stage_id?: string
|
||||
status?: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetPipelineDealsOutput {
|
||||
deals: PipedriveDeal[]
|
||||
metadata: {
|
||||
operation: 'get_pipeline_deals'
|
||||
pipelineId: string
|
||||
totalItems: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetPipelineDealsResponse extends ToolResponse {
|
||||
output: PipedriveGetPipelineDealsOutput
|
||||
}
|
||||
|
||||
// GET All Projects (or single project if project_id provided)
|
||||
export interface PipedriveGetProjectsParams {
|
||||
accessToken: string
|
||||
project_id?: string
|
||||
status?: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetProjectsOutput {
|
||||
projects?: PipedriveProject[]
|
||||
project?: PipedriveProject
|
||||
metadata: {
|
||||
operation: 'get_projects'
|
||||
totalItems?: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetProjectsResponse extends ToolResponse {
|
||||
output: PipedriveGetProjectsOutput
|
||||
}
|
||||
|
||||
// CREATE Project
|
||||
export interface PipedriveCreateProjectParams {
|
||||
accessToken: string
|
||||
title: string
|
||||
description?: string
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
}
|
||||
|
||||
export interface PipedriveCreateProjectOutput {
|
||||
project: PipedriveProject
|
||||
metadata: {
|
||||
operation: 'create_project'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveCreateProjectResponse extends ToolResponse {
|
||||
output: PipedriveCreateProjectOutput
|
||||
}
|
||||
|
||||
// GET All Activities
|
||||
export interface PipedriveGetActivitiesParams {
|
||||
accessToken: string
|
||||
deal_id?: string
|
||||
person_id?: string
|
||||
org_id?: string
|
||||
type?: string
|
||||
done?: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetActivitiesOutput {
|
||||
activities: PipedriveActivity[]
|
||||
metadata: {
|
||||
operation: 'get_activities'
|
||||
totalItems: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetActivitiesResponse extends ToolResponse {
|
||||
output: PipedriveGetActivitiesOutput
|
||||
}
|
||||
|
||||
// CREATE Activity
|
||||
export interface PipedriveCreateActivityParams {
|
||||
accessToken: string
|
||||
subject: string
|
||||
type: string
|
||||
due_date: string
|
||||
due_time?: string
|
||||
duration?: string
|
||||
deal_id?: string
|
||||
person_id?: string
|
||||
org_id?: string
|
||||
note?: string
|
||||
}
|
||||
|
||||
export interface PipedriveCreateActivityOutput {
|
||||
activity: PipedriveActivity
|
||||
metadata: {
|
||||
operation: 'create_activity'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveCreateActivityResponse extends ToolResponse {
|
||||
output: PipedriveCreateActivityOutput
|
||||
}
|
||||
|
||||
// UPDATE Activity
|
||||
export interface PipedriveUpdateActivityParams {
|
||||
accessToken: string
|
||||
activity_id: string
|
||||
subject?: string
|
||||
due_date?: string
|
||||
due_time?: string
|
||||
duration?: string
|
||||
done?: string
|
||||
note?: string
|
||||
}
|
||||
|
||||
export interface PipedriveUpdateActivityOutput {
|
||||
activity: PipedriveActivity
|
||||
metadata: {
|
||||
operation: 'update_activity'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveUpdateActivityResponse extends ToolResponse {
|
||||
output: PipedriveUpdateActivityOutput
|
||||
}
|
||||
|
||||
// GET Leads
|
||||
export interface PipedriveGetLeadsParams {
|
||||
accessToken: string
|
||||
lead_id?: string
|
||||
archived?: string
|
||||
owner_id?: string
|
||||
person_id?: string
|
||||
organization_id?: string
|
||||
limit?: string
|
||||
}
|
||||
|
||||
export interface PipedriveGetLeadsOutput {
|
||||
leads?: PipedriveLead[]
|
||||
lead?: PipedriveLead
|
||||
metadata: {
|
||||
operation: 'get_leads'
|
||||
totalItems?: number
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveGetLeadsResponse extends ToolResponse {
|
||||
output: PipedriveGetLeadsOutput
|
||||
}
|
||||
|
||||
// CREATE Lead
|
||||
export interface PipedriveCreateLeadParams {
|
||||
accessToken: string
|
||||
title: string
|
||||
person_id?: string
|
||||
organization_id?: string
|
||||
owner_id?: string
|
||||
value_amount?: string
|
||||
value_currency?: string
|
||||
expected_close_date?: string
|
||||
visible_to?: string
|
||||
}
|
||||
|
||||
export interface PipedriveCreateLeadOutput {
|
||||
lead: PipedriveLead
|
||||
metadata: {
|
||||
operation: 'create_lead'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveCreateLeadResponse extends ToolResponse {
|
||||
output: PipedriveCreateLeadOutput
|
||||
}
|
||||
|
||||
// UPDATE Lead
|
||||
export interface PipedriveUpdateLeadParams {
|
||||
accessToken: string
|
||||
lead_id: string
|
||||
title?: string
|
||||
person_id?: string
|
||||
organization_id?: string
|
||||
owner_id?: string
|
||||
value_amount?: string
|
||||
value_currency?: string
|
||||
expected_close_date?: string
|
||||
is_archived?: string
|
||||
}
|
||||
|
||||
export interface PipedriveUpdateLeadOutput {
|
||||
lead: PipedriveLead
|
||||
metadata: {
|
||||
operation: 'update_lead'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveUpdateLeadResponse extends ToolResponse {
|
||||
output: PipedriveUpdateLeadOutput
|
||||
}
|
||||
|
||||
// DELETE Lead
|
||||
export interface PipedriveDeleteLeadParams {
|
||||
accessToken: string
|
||||
lead_id: string
|
||||
}
|
||||
|
||||
export interface PipedriveDeleteLeadOutput {
|
||||
data: any
|
||||
metadata: {
|
||||
operation: 'delete_lead'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export interface PipedriveDeleteLeadResponse extends ToolResponse {
|
||||
output: PipedriveDeleteLeadOutput
|
||||
}
|
||||
|
||||
// Union type of all responses
|
||||
export type PipedriveResponse =
|
||||
| PipedriveGetAllDealsResponse
|
||||
| PipedriveGetDealResponse
|
||||
| PipedriveCreateDealResponse
|
||||
| PipedriveUpdateDealResponse
|
||||
| PipedriveGetFilesResponse
|
||||
| PipedriveGetMailMessagesResponse
|
||||
| PipedriveGetMailThreadResponse
|
||||
| PipedriveGetPipelinesResponse
|
||||
| PipedriveGetPipelineDealsResponse
|
||||
| PipedriveGetProjectsResponse
|
||||
| PipedriveCreateProjectResponse
|
||||
| PipedriveGetActivitiesResponse
|
||||
| PipedriveCreateActivityResponse
|
||||
| PipedriveUpdateActivityResponse
|
||||
| PipedriveGetLeadsResponse
|
||||
| PipedriveCreateLeadResponse
|
||||
| PipedriveUpdateLeadResponse
|
||||
| PipedriveDeleteLeadResponse
|
||||
136
apps/sim/tools/pipedrive/update_activity.ts
Normal file
136
apps/sim/tools/pipedrive/update_activity.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveUpdateActivityParams,
|
||||
PipedriveUpdateActivityResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveUpdateActivity')
|
||||
|
||||
export const pipedriveUpdateActivityTool: ToolConfig<
|
||||
PipedriveUpdateActivityParams,
|
||||
PipedriveUpdateActivityResponse
|
||||
> = {
|
||||
id: 'pipedrive_update_activity',
|
||||
name: 'Update Activity in Pipedrive',
|
||||
description: 'Update an existing activity (task) in Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
activity_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the activity to update',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New subject/title for the activity',
|
||||
},
|
||||
due_date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New due date in YYYY-MM-DD format',
|
||||
},
|
||||
due_time: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New due time in HH:MM format',
|
||||
},
|
||||
duration: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New duration in HH:MM format',
|
||||
},
|
||||
done: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mark as done: 0 for not done, 1 for done',
|
||||
},
|
||||
note: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New notes for the activity',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.pipedrive.com/v1/activities/${params.activity_id}`,
|
||||
method: 'PUT',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
|
||||
if (params.subject) body.subject = params.subject
|
||||
if (params.due_date) body.due_date = params.due_date
|
||||
if (params.due_time) body.due_time = params.due_time
|
||||
if (params.duration) body.duration = params.duration
|
||||
if (params.done !== undefined) body.done = params.done === '1' ? 1 : 0
|
||||
if (params.note) body.note = params.note
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to update activity in Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
activity: data.data,
|
||||
metadata: {
|
||||
operation: 'update_activity' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Updated activity details',
|
||||
properties: {
|
||||
activity: {
|
||||
type: 'object',
|
||||
description: 'The updated activity object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
129
apps/sim/tools/pipedrive/update_deal.ts
Normal file
129
apps/sim/tools/pipedrive/update_deal.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveUpdateDealParams,
|
||||
PipedriveUpdateDealResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveUpdateDeal')
|
||||
|
||||
export const pipedriveUpdateDealTool: ToolConfig<
|
||||
PipedriveUpdateDealParams,
|
||||
PipedriveUpdateDealResponse
|
||||
> = {
|
||||
id: 'pipedrive_update_deal',
|
||||
name: 'Update Deal in Pipedrive',
|
||||
description: 'Update an existing deal in Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
deal_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the deal to update',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New title for the deal',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New monetary value for the deal',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New status: open, won, lost',
|
||||
},
|
||||
stage_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New stage ID for the deal',
|
||||
},
|
||||
expected_close_date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New expected close date in YYYY-MM-DD format',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.pipedrive.com/api/v2/deals/${params.deal_id}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
|
||||
if (params.title) body.title = params.title
|
||||
if (params.value) body.value = Number(params.value)
|
||||
if (params.status) body.status = params.status
|
||||
if (params.stage_id) body.stage_id = Number(params.stage_id)
|
||||
if (params.expected_close_date) body.expected_close_date = params.expected_close_date
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to update deal in Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deal: data.data,
|
||||
metadata: {
|
||||
operation: 'update_deal' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Updated deal details',
|
||||
properties: {
|
||||
deal: {
|
||||
type: 'object',
|
||||
description: 'The updated deal object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
162
apps/sim/tools/pipedrive/update_lead.ts
Normal file
162
apps/sim/tools/pipedrive/update_lead.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
PipedriveUpdateLeadParams,
|
||||
PipedriveUpdateLeadResponse,
|
||||
} from '@/tools/pipedrive/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('PipedriveUpdateLead')
|
||||
|
||||
export const pipedriveUpdateLeadTool: ToolConfig<
|
||||
PipedriveUpdateLeadParams,
|
||||
PipedriveUpdateLeadResponse
|
||||
> = {
|
||||
id: 'pipedrive_update_lead',
|
||||
name: 'Update Lead in Pipedrive',
|
||||
description: 'Update an existing lead in Pipedrive',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'pipedrive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Pipedrive API',
|
||||
},
|
||||
lead_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the lead to update',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New name for the lead',
|
||||
},
|
||||
person_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New person ID',
|
||||
},
|
||||
organization_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New organization ID',
|
||||
},
|
||||
owner_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New owner user ID',
|
||||
},
|
||||
value_amount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New value amount',
|
||||
},
|
||||
value_currency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New currency code (e.g., USD, EUR)',
|
||||
},
|
||||
expected_close_date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'New expected close date in YYYY-MM-DD format',
|
||||
},
|
||||
is_archived: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Archive the lead: true or false',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.pipedrive.com/v1/leads/${params.lead_id}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
|
||||
if (params.title) body.title = params.title
|
||||
if (params.person_id) body.person_id = Number(params.person_id)
|
||||
if (params.organization_id) body.organization_id = Number(params.organization_id)
|
||||
if (params.owner_id) body.owner_id = Number(params.owner_id)
|
||||
|
||||
// Build value object if both amount and currency are provided
|
||||
if (params.value_amount && params.value_currency) {
|
||||
body.value = {
|
||||
amount: Number(params.value_amount),
|
||||
currency: params.value_currency,
|
||||
}
|
||||
}
|
||||
|
||||
if (params.expected_close_date) body.expected_close_date = params.expected_close_date
|
||||
if (params.is_archived) body.is_archived = params.is_archived === 'true'
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('Pipedrive API request failed', { data })
|
||||
throw new Error(data.error || 'Failed to update lead in Pipedrive')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
lead: data.data,
|
||||
metadata: {
|
||||
operation: 'update_lead' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Updated lead details',
|
||||
properties: {
|
||||
lead: {
|
||||
type: 'object',
|
||||
description: 'The updated lead object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -5,6 +5,14 @@ import {
|
||||
airtableUpdateRecordTool,
|
||||
} from '@/tools/airtable'
|
||||
import { arxivGetAuthorPapersTool, arxivGetPaperTool, arxivSearchTool } from '@/tools/arxiv'
|
||||
import {
|
||||
asanaAddCommentTool,
|
||||
asanaCreateTaskTool,
|
||||
asanaGetProjectsTool,
|
||||
asanaGetTaskTool,
|
||||
asanaSearchTasksTool,
|
||||
asanaUpdateTaskTool,
|
||||
} from '@/tools/asana'
|
||||
import { browserUseRunTaskTool } from '@/tools/browser_use'
|
||||
import { clayPopulateTool } from '@/tools/clay'
|
||||
import {
|
||||
@@ -173,6 +181,20 @@ import {
|
||||
} from '@/tools/google_vault'
|
||||
import { guardrailsValidateTool } from '@/tools/guardrails'
|
||||
import { requestTool as httpRequest } from '@/tools/http'
|
||||
import {
|
||||
hubspotCreateCompanyTool,
|
||||
hubspotCreateContactTool,
|
||||
hubspotGetCompanyTool,
|
||||
hubspotGetContactTool,
|
||||
hubspotGetUsersTool,
|
||||
hubspotListCompaniesTool,
|
||||
hubspotListContactsTool,
|
||||
hubspotListDealsTool,
|
||||
hubspotSearchCompaniesTool,
|
||||
hubspotSearchContactsTool,
|
||||
hubspotUpdateCompanyTool,
|
||||
hubspotUpdateContactTool,
|
||||
} from '@/tools/hubspot'
|
||||
import { huggingfaceChatTool } from '@/tools/huggingface'
|
||||
import {
|
||||
hunterCompaniesFindTool,
|
||||
@@ -384,6 +406,26 @@ import {
|
||||
pineconeSearchVectorTool,
|
||||
pineconeUpsertTextTool,
|
||||
} from '@/tools/pinecone'
|
||||
import {
|
||||
pipedriveCreateActivityTool,
|
||||
pipedriveCreateDealTool,
|
||||
pipedriveCreateLeadTool,
|
||||
pipedriveCreateProjectTool,
|
||||
pipedriveDeleteLeadTool,
|
||||
pipedriveGetActivitiesTool,
|
||||
pipedriveGetAllDealsTool,
|
||||
pipedriveGetDealTool,
|
||||
pipedriveGetFilesTool,
|
||||
pipedriveGetLeadsTool,
|
||||
pipedriveGetMailMessagesTool,
|
||||
pipedriveGetMailThreadTool,
|
||||
pipedriveGetPipelineDealsTool,
|
||||
pipedriveGetPipelinesTool,
|
||||
pipedriveGetProjectsTool,
|
||||
pipedriveUpdateActivityTool,
|
||||
pipedriveUpdateDealTool,
|
||||
pipedriveUpdateLeadTool,
|
||||
} from '@/tools/pipedrive'
|
||||
import {
|
||||
deleteTool as postgresDeleteTool,
|
||||
executeTool as postgresExecuteTool,
|
||||
@@ -415,6 +457,32 @@ import {
|
||||
s3ListObjectsTool,
|
||||
s3PutObjectTool,
|
||||
} from '@/tools/s3'
|
||||
import {
|
||||
salesforceCreateAccountTool,
|
||||
salesforceCreateCaseTool,
|
||||
salesforceCreateContactTool,
|
||||
salesforceCreateLeadTool,
|
||||
salesforceCreateOpportunityTool,
|
||||
salesforceCreateTaskTool,
|
||||
salesforceDeleteAccountTool,
|
||||
salesforceDeleteCaseTool,
|
||||
salesforceDeleteContactTool,
|
||||
salesforceDeleteLeadTool,
|
||||
salesforceDeleteOpportunityTool,
|
||||
salesforceDeleteTaskTool,
|
||||
salesforceGetAccountsTool,
|
||||
salesforceGetCasesTool,
|
||||
salesforceGetContactsTool,
|
||||
salesforceGetLeadsTool,
|
||||
salesforceGetOpportunitiesTool,
|
||||
salesforceGetTasksTool,
|
||||
salesforceUpdateAccountTool,
|
||||
salesforceUpdateCaseTool,
|
||||
salesforceUpdateContactTool,
|
||||
salesforceUpdateLeadTool,
|
||||
salesforceUpdateOpportunityTool,
|
||||
salesforceUpdateTaskTool,
|
||||
} from '@/tools/salesforce'
|
||||
import { searchTool as serperSearch } from '@/tools/serper'
|
||||
import {
|
||||
sharepointAddListItemTool,
|
||||
@@ -523,6 +591,14 @@ import {
|
||||
telegramSendVideoTool,
|
||||
} from '@/tools/telegram'
|
||||
import { thinkingTool } from '@/tools/thinking'
|
||||
import {
|
||||
trelloAddCommentTool,
|
||||
trelloCreateCardTool,
|
||||
trelloGetActionsTool,
|
||||
trelloListCardsTool,
|
||||
trelloListListsTool,
|
||||
trelloUpdateCardTool,
|
||||
} from '@/tools/trello'
|
||||
import { sendSMSTool } from '@/tools/twilio'
|
||||
import { getRecordingTool, listCallsTool, makeCallTool } from '@/tools/twilio_voice'
|
||||
import {
|
||||
@@ -588,6 +664,12 @@ export const tools: Record<string, ToolConfig> = {
|
||||
arxiv_search: arxivSearchTool,
|
||||
arxiv_get_paper: arxivGetPaperTool,
|
||||
arxiv_get_author_papers: arxivGetAuthorPapersTool,
|
||||
asana_get_task: asanaGetTaskTool,
|
||||
asana_create_task: asanaCreateTaskTool,
|
||||
asana_update_task: asanaUpdateTaskTool,
|
||||
asana_get_projects: asanaGetProjectsTool,
|
||||
asana_search_tasks: asanaSearchTasksTool,
|
||||
asana_add_comment: asanaAddCommentTool,
|
||||
browser_use_run_task: browserUseRunTaskTool,
|
||||
openai_embeddings: openAIEmbeddings,
|
||||
http_request: httpRequest,
|
||||
@@ -709,6 +791,24 @@ export const tools: Record<string, ToolConfig> = {
|
||||
pinecone_search_text: pineconeSearchTextTool,
|
||||
pinecone_search_vector: pineconeSearchVectorTool,
|
||||
pinecone_upsert_text: pineconeUpsertTextTool,
|
||||
pipedrive_create_activity: pipedriveCreateActivityTool,
|
||||
pipedrive_create_deal: pipedriveCreateDealTool,
|
||||
pipedrive_create_lead: pipedriveCreateLeadTool,
|
||||
pipedrive_create_project: pipedriveCreateProjectTool,
|
||||
pipedrive_delete_lead: pipedriveDeleteLeadTool,
|
||||
pipedrive_get_activities: pipedriveGetActivitiesTool,
|
||||
pipedrive_get_all_deals: pipedriveGetAllDealsTool,
|
||||
pipedrive_get_deal: pipedriveGetDealTool,
|
||||
pipedrive_get_files: pipedriveGetFilesTool,
|
||||
pipedrive_get_leads: pipedriveGetLeadsTool,
|
||||
pipedrive_get_mail_messages: pipedriveGetMailMessagesTool,
|
||||
pipedrive_get_mail_thread: pipedriveGetMailThreadTool,
|
||||
pipedrive_get_pipeline_deals: pipedriveGetPipelineDealsTool,
|
||||
pipedrive_get_pipelines: pipedriveGetPipelinesTool,
|
||||
pipedrive_get_projects: pipedriveGetProjectsTool,
|
||||
pipedrive_update_activity: pipedriveUpdateActivityTool,
|
||||
pipedrive_update_deal: pipedriveUpdateDealTool,
|
||||
pipedrive_update_lead: pipedriveUpdateLeadTool,
|
||||
postgresql_query: postgresQueryTool,
|
||||
postgresql_insert: postgresInsertTool,
|
||||
postgresql_update: postgresUpdateTool,
|
||||
@@ -823,6 +923,12 @@ export const tools: Record<string, ToolConfig> = {
|
||||
confluence_list_labels: confluenceListLabelsTool,
|
||||
confluence_get_space: confluenceGetSpaceTool,
|
||||
confluence_list_spaces: confluenceListSpacesTool,
|
||||
trello_list_lists: trelloListListsTool,
|
||||
trello_list_cards: trelloListCardsTool,
|
||||
trello_create_card: trelloCreateCardTool,
|
||||
trello_update_card: trelloUpdateCardTool,
|
||||
trello_get_actions: trelloGetActionsTool,
|
||||
trello_add_comment: trelloAddCommentTool,
|
||||
twilio_send_sms: sendSMSTool,
|
||||
twilio_voice_make_call: makeCallTool,
|
||||
twilio_voice_list_calls: listCallsTool,
|
||||
@@ -1065,6 +1171,18 @@ export const tools: Record<string, ToolConfig> = {
|
||||
hunter_email_verifier: hunterEmailVerifierTool,
|
||||
hunter_companies_find: hunterCompaniesFindTool,
|
||||
hunter_email_count: hunterEmailCountTool,
|
||||
hubspot_create_company: hubspotCreateCompanyTool,
|
||||
hubspot_create_contact: hubspotCreateContactTool,
|
||||
hubspot_get_company: hubspotGetCompanyTool,
|
||||
hubspot_get_contact: hubspotGetContactTool,
|
||||
hubspot_get_users: hubspotGetUsersTool,
|
||||
hubspot_list_companies: hubspotListCompaniesTool,
|
||||
hubspot_list_contacts: hubspotListContactsTool,
|
||||
hubspot_list_deals: hubspotListDealsTool,
|
||||
hubspot_search_companies: hubspotSearchCompaniesTool,
|
||||
hubspot_search_contacts: hubspotSearchContactsTool,
|
||||
hubspot_update_company: hubspotUpdateCompanyTool,
|
||||
hubspot_update_contact: hubspotUpdateContactTool,
|
||||
sharepoint_create_page: sharepointCreatePageTool,
|
||||
sharepoint_read_page: sharepointReadPageTool,
|
||||
sharepoint_list_sites: sharepointListSitesTool,
|
||||
@@ -1123,4 +1241,28 @@ export const tools: Record<string, ToolConfig> = {
|
||||
stripe_search_prices: stripeSearchPricesTool,
|
||||
stripe_retrieve_event: stripeRetrieveEventTool,
|
||||
stripe_list_events: stripeListEventsTool,
|
||||
salesforce_get_accounts: salesforceGetAccountsTool,
|
||||
salesforce_create_account: salesforceCreateAccountTool,
|
||||
salesforce_update_account: salesforceUpdateAccountTool,
|
||||
salesforce_delete_account: salesforceDeleteAccountTool,
|
||||
salesforce_get_contacts: salesforceGetContactsTool,
|
||||
salesforce_create_contact: salesforceCreateContactTool,
|
||||
salesforce_update_contact: salesforceUpdateContactTool,
|
||||
salesforce_delete_contact: salesforceDeleteContactTool,
|
||||
salesforce_get_leads: salesforceGetLeadsTool,
|
||||
salesforce_create_lead: salesforceCreateLeadTool,
|
||||
salesforce_update_lead: salesforceUpdateLeadTool,
|
||||
salesforce_delete_lead: salesforceDeleteLeadTool,
|
||||
salesforce_get_opportunities: salesforceGetOpportunitiesTool,
|
||||
salesforce_create_opportunity: salesforceCreateOpportunityTool,
|
||||
salesforce_update_opportunity: salesforceUpdateOpportunityTool,
|
||||
salesforce_delete_opportunity: salesforceDeleteOpportunityTool,
|
||||
salesforce_get_cases: salesforceGetCasesTool,
|
||||
salesforce_create_case: salesforceCreateCaseTool,
|
||||
salesforce_update_case: salesforceUpdateCaseTool,
|
||||
salesforce_delete_case: salesforceDeleteCaseTool,
|
||||
salesforce_get_tasks: salesforceGetTasksTool,
|
||||
salesforce_create_task: salesforceCreateTaskTool,
|
||||
salesforce_update_task: salesforceUpdateTaskTool,
|
||||
salesforce_delete_task: salesforceDeleteTaskTool,
|
||||
}
|
||||
|
||||
315
apps/sim/tools/salesforce/cases.ts
Normal file
315
apps/sim/tools/salesforce/cases.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceCases')
|
||||
|
||||
function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
|
||||
if (instanceUrl) return instanceUrl
|
||||
if (idToken) {
|
||||
try {
|
||||
const base64Url = idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) return match[1]
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') return match[1]
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
// Get Cases
|
||||
export const salesforceGetCasesTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_get_cases',
|
||||
name: 'Get Cases from Salesforce',
|
||||
description: 'Get case(s) from Salesforce',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
caseId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Case ID (optional)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Max results (default: 100)',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated fields',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Order by field',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
if (params.caseId) {
|
||||
const fields =
|
||||
params.fields || 'Id,CaseNumber,Subject,Status,Priority,Origin,ContactId,AccountId'
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Case/${params.caseId}?fields=${fields}`
|
||||
}
|
||||
const limit = params.limit ? Number.parseInt(params.limit) : 100
|
||||
const fields =
|
||||
params.fields || 'Id,CaseNumber,Subject,Status,Priority,Origin,ContactId,AccountId'
|
||||
const orderBy = params.orderBy || 'CreatedDate DESC'
|
||||
const query = `SELECT ${fields} FROM Case ORDER BY ${orderBy} LIMIT ${limit}`
|
||||
return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch cases')
|
||||
if (params.caseId) {
|
||||
return {
|
||||
success: true,
|
||||
output: { case: data, metadata: { operation: 'get_cases' }, success: true },
|
||||
}
|
||||
}
|
||||
const cases = data.records || []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
cases,
|
||||
paging: {
|
||||
nextRecordsUrl: data.nextRecordsUrl,
|
||||
totalSize: data.totalSize || cases.length,
|
||||
done: data.done !== false,
|
||||
},
|
||||
metadata: { operation: 'get_cases', totalReturned: cases.length, hasMore: !data.done },
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Case data' },
|
||||
},
|
||||
}
|
||||
|
||||
// Create Case
|
||||
export const salesforceCreateCaseTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_create_case',
|
||||
name: 'Create Case in Salesforce',
|
||||
description: 'Create a new case',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Case subject (required)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Status (e.g., New, Working, Escalated)',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Priority (e.g., Low, Medium, High)',
|
||||
},
|
||||
origin: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Origin (e.g., Phone, Email, Web)',
|
||||
},
|
||||
contactId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Contact ID',
|
||||
},
|
||||
accountId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Account ID',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { Subject: params.subject }
|
||||
if (params.status) body.Status = params.status
|
||||
if (params.priority) body.Priority = params.priority
|
||||
if (params.origin) body.Origin = params.origin
|
||||
if (params.contactId) body.ContactId = params.contactId
|
||||
if (params.accountId) body.AccountId = params.accountId
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create case')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
success: data.success,
|
||||
created: true,
|
||||
metadata: { operation: 'create_case' },
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Created case' },
|
||||
},
|
||||
}
|
||||
|
||||
// Update Case
|
||||
export const salesforceUpdateCaseTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_update_case',
|
||||
name: 'Update Case in Salesforce',
|
||||
description: 'Update an existing case',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
caseId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Case ID (required)',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Case subject',
|
||||
},
|
||||
status: { type: 'string', required: false, visibility: 'user-only', description: 'Status' },
|
||||
priority: { type: 'string', required: false, visibility: 'user-only', description: 'Priority' },
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case/${params.caseId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.subject) body.Subject = params.subject
|
||||
if (params.status) body.Status = params.status
|
||||
if (params.priority) body.Priority = params.priority
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to update case')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { id: params.caseId, updated: true, metadata: { operation: 'update_case' } },
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Updated case' },
|
||||
},
|
||||
}
|
||||
|
||||
// Delete Case
|
||||
export const salesforceDeleteCaseTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_delete_case',
|
||||
name: 'Delete Case from Salesforce',
|
||||
description: 'Delete a case',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
caseId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Case ID (required)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case/${params.caseId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to delete case')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { id: params.caseId, deleted: true, metadata: { operation: 'delete_case' } },
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Deleted case' },
|
||||
},
|
||||
}
|
||||
658
apps/sim/tools/salesforce/contacts.ts
Normal file
658
apps/sim/tools/salesforce/contacts.ts
Normal file
@@ -0,0 +1,658 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceContacts')
|
||||
|
||||
// Helper to extract instance URL from idToken
|
||||
function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
|
||||
if (instanceUrl) return instanceUrl
|
||||
|
||||
if (idToken) {
|
||||
try {
|
||||
const base64Url = idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) return match[1]
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') {
|
||||
return match[1]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
// Get Contacts (with optional contactId)
|
||||
export interface SalesforceGetContactsParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
contactId?: string
|
||||
limit?: string
|
||||
fields?: string
|
||||
orderBy?: string
|
||||
}
|
||||
|
||||
export interface SalesforceGetContactsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
contacts?: any[]
|
||||
contact?: any
|
||||
paging?: {
|
||||
nextRecordsUrl?: string
|
||||
totalSize: number
|
||||
done: boolean
|
||||
}
|
||||
metadata: {
|
||||
operation: 'get_contacts'
|
||||
totalReturned?: number
|
||||
hasMore?: boolean
|
||||
singleContact?: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const salesforceGetContactsTool: ToolConfig<
|
||||
SalesforceGetContactsParams,
|
||||
SalesforceGetContactsResponse
|
||||
> = {
|
||||
id: 'salesforce_get_contacts',
|
||||
name: 'Get Contacts from Salesforce',
|
||||
description: 'Get contact(s) from Salesforce - single contact if ID provided, or list if not',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'salesforce',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
contactId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Contact ID (if provided, returns single contact)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results (default: 100, max: 2000). Only for list query.',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated fields (e.g., "Id,FirstName,LastName,Email,Phone")',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Order by field (e.g., "LastName ASC"). Only for list query.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
|
||||
// Single contact by ID
|
||||
if (params.contactId) {
|
||||
const fields =
|
||||
params.fields || 'Id,FirstName,LastName,Email,Phone,AccountId,Title,Department'
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}?fields=${fields}`
|
||||
}
|
||||
|
||||
// List contacts with SOQL query
|
||||
const limit = params.limit ? Number.parseInt(params.limit) : 100
|
||||
const fields = params.fields || 'Id,FirstName,LastName,Email,Phone,AccountId,Title,Department'
|
||||
const orderBy = params.orderBy || 'LastName ASC'
|
||||
const query = `SELECT ${fields} FROM Contact ORDER BY ${orderBy} LIMIT ${limit}`
|
||||
const encodedQuery = encodeURIComponent(query)
|
||||
|
||||
return `${instanceUrl}/services/data/v59.0/query?q=${encodedQuery}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Salesforce API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
data[0]?.message || data.message || 'Failed to fetch contacts from Salesforce'
|
||||
)
|
||||
}
|
||||
|
||||
// Single contact response
|
||||
if (params?.contactId) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contact: data,
|
||||
metadata: {
|
||||
operation: 'get_contacts' as const,
|
||||
singleContact: true,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// List contacts response
|
||||
const contacts = data.records || []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contacts,
|
||||
paging: {
|
||||
nextRecordsUrl: data.nextRecordsUrl,
|
||||
totalSize: data.totalSize || contacts.length,
|
||||
done: data.done !== false,
|
||||
},
|
||||
metadata: {
|
||||
operation: 'get_contacts' as const,
|
||||
totalReturned: contacts.length,
|
||||
hasMore: !data.done,
|
||||
singleContact: false,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Contact(s) data',
|
||||
properties: {
|
||||
contacts: { type: 'array', description: 'Array of contacts (list query)' },
|
||||
contact: { type: 'object', description: 'Single contact (by ID)' },
|
||||
paging: { type: 'object', description: 'Pagination info (list query)' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create Contact
|
||||
export interface SalesforceCreateContactParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
lastName: string
|
||||
firstName?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
accountId?: string
|
||||
title?: string
|
||||
department?: string
|
||||
mailingStreet?: string
|
||||
mailingCity?: string
|
||||
mailingState?: string
|
||||
mailingPostalCode?: string
|
||||
mailingCountry?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface SalesforceCreateContactResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
success: boolean
|
||||
created: boolean
|
||||
metadata: { operation: 'create_contact' }
|
||||
}
|
||||
}
|
||||
|
||||
export const salesforceCreateContactTool: ToolConfig<
|
||||
SalesforceCreateContactParams,
|
||||
SalesforceCreateContactResponse
|
||||
> = {
|
||||
id: 'salesforce_create_contact',
|
||||
name: 'Create Contact in Salesforce',
|
||||
description: 'Create a new contact in Salesforce CRM',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Last name (required)',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'First name',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Email address',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Phone number',
|
||||
},
|
||||
accountId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Account ID to associate contact with',
|
||||
},
|
||||
title: { type: 'string', required: false, visibility: 'user-only', description: 'Job title' },
|
||||
department: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Department',
|
||||
},
|
||||
mailingStreet: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing street',
|
||||
},
|
||||
mailingCity: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing city',
|
||||
},
|
||||
mailingState: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing state',
|
||||
},
|
||||
mailingPostalCode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing postal code',
|
||||
},
|
||||
mailingCountry: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing country',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Contact description',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Contact`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { LastName: params.lastName }
|
||||
|
||||
if (params.firstName) body.FirstName = params.firstName
|
||||
if (params.email) body.Email = params.email
|
||||
if (params.phone) body.Phone = params.phone
|
||||
if (params.accountId) body.AccountId = params.accountId
|
||||
if (params.title) body.Title = params.title
|
||||
if (params.department) body.Department = params.department
|
||||
if (params.mailingStreet) body.MailingStreet = params.mailingStreet
|
||||
if (params.mailingCity) body.MailingCity = params.mailingCity
|
||||
if (params.mailingState) body.MailingState = params.mailingState
|
||||
if (params.mailingPostalCode) body.MailingPostalCode = params.mailingPostalCode
|
||||
if (params.mailingCountry) body.MailingCountry = params.mailingCountry
|
||||
if (params.description) body.Description = params.description
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Salesforce API request failed', { data, status: response.status })
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to create contact in Salesforce')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
success: data.success,
|
||||
created: true,
|
||||
metadata: { operation: 'create_contact' as const },
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created contact data',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Created contact ID' },
|
||||
success: { type: 'boolean', description: 'Salesforce operation success' },
|
||||
created: { type: 'boolean', description: 'Whether contact was created' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Update Contact
|
||||
export interface SalesforceUpdateContactParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
contactId: string
|
||||
lastName?: string
|
||||
firstName?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
accountId?: string
|
||||
title?: string
|
||||
department?: string
|
||||
mailingStreet?: string
|
||||
mailingCity?: string
|
||||
mailingState?: string
|
||||
mailingPostalCode?: string
|
||||
mailingCountry?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface SalesforceUpdateContactResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
updated: boolean
|
||||
metadata: { operation: 'update_contact' }
|
||||
}
|
||||
}
|
||||
|
||||
export const salesforceUpdateContactTool: ToolConfig<
|
||||
SalesforceUpdateContactParams,
|
||||
SalesforceUpdateContactResponse
|
||||
> = {
|
||||
id: 'salesforce_update_contact',
|
||||
name: 'Update Contact in Salesforce',
|
||||
description: 'Update an existing contact in Salesforce CRM',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
contactId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Contact ID to update (required)',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Last name',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'First name',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Email address',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Phone number',
|
||||
},
|
||||
accountId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Account ID to associate with',
|
||||
},
|
||||
title: { type: 'string', required: false, visibility: 'user-only', description: 'Job title' },
|
||||
department: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Department',
|
||||
},
|
||||
mailingStreet: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing street',
|
||||
},
|
||||
mailingCity: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing city',
|
||||
},
|
||||
mailingState: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing state',
|
||||
},
|
||||
mailingPostalCode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing postal code',
|
||||
},
|
||||
mailingCountry: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Mailing country',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
|
||||
if (params.lastName) body.LastName = params.lastName
|
||||
if (params.firstName) body.FirstName = params.firstName
|
||||
if (params.email) body.Email = params.email
|
||||
if (params.phone) body.Phone = params.phone
|
||||
if (params.accountId) body.AccountId = params.accountId
|
||||
if (params.title) body.Title = params.title
|
||||
if (params.department) body.Department = params.department
|
||||
if (params.mailingStreet) body.MailingStreet = params.mailingStreet
|
||||
if (params.mailingCity) body.MailingCity = params.mailingCity
|
||||
if (params.mailingState) body.MailingState = params.mailingState
|
||||
if (params.mailingPostalCode) body.MailingPostalCode = params.mailingPostalCode
|
||||
if (params.mailingCountry) body.MailingCountry = params.mailingCountry
|
||||
if (params.description) body.Description = params.description
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
logger.error('Salesforce API request failed', { data, status: response.status })
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to update contact in Salesforce')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: params?.contactId || '',
|
||||
updated: true,
|
||||
metadata: { operation: 'update_contact' as const },
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Updated contact data',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Updated contact ID' },
|
||||
updated: { type: 'boolean', description: 'Whether contact was updated' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Delete Contact
|
||||
export interface SalesforceDeleteContactParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
contactId: string
|
||||
}
|
||||
|
||||
export interface SalesforceDeleteContactResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
deleted: boolean
|
||||
metadata: { operation: 'delete_contact' }
|
||||
}
|
||||
}
|
||||
|
||||
export const salesforceDeleteContactTool: ToolConfig<
|
||||
SalesforceDeleteContactParams,
|
||||
SalesforceDeleteContactResponse
|
||||
> = {
|
||||
id: 'salesforce_delete_contact',
|
||||
name: 'Delete Contact from Salesforce',
|
||||
description: 'Delete a contact from Salesforce CRM',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
contactId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Contact ID to delete (required)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
logger.error('Salesforce API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
data[0]?.message || data.message || 'Failed to delete contact from Salesforce'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: params?.contactId || '',
|
||||
deleted: true,
|
||||
metadata: { operation: 'delete_contact' as const },
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Deleted contact data',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Deleted contact ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether contact was deleted' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
253
apps/sim/tools/salesforce/create_account.ts
Normal file
253
apps/sim/tools/salesforce/create_account.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceCreateAccount')
|
||||
|
||||
export interface SalesforceCreateAccountParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
name: string
|
||||
type?: string
|
||||
industry?: string
|
||||
phone?: string
|
||||
website?: string
|
||||
billingStreet?: string
|
||||
billingCity?: string
|
||||
billingState?: string
|
||||
billingPostalCode?: string
|
||||
billingCountry?: string
|
||||
description?: string
|
||||
annualRevenue?: string
|
||||
numberOfEmployees?: string
|
||||
}
|
||||
|
||||
export interface SalesforceCreateAccountResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
success: boolean
|
||||
created: boolean
|
||||
metadata: {
|
||||
operation: 'create_account'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const salesforceCreateAccountTool: ToolConfig<
|
||||
SalesforceCreateAccountParams,
|
||||
SalesforceCreateAccountResponse
|
||||
> = {
|
||||
id: 'salesforce_create_account',
|
||||
name: 'Create Account in Salesforce',
|
||||
description: 'Create a new account in Salesforce CRM',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'salesforce',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
idToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Account name (required)',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Account type (e.g., Customer, Partner, Prospect)',
|
||||
},
|
||||
industry: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Industry (e.g., Technology, Healthcare, Finance)',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Phone number',
|
||||
},
|
||||
website: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Website URL',
|
||||
},
|
||||
billingStreet: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Billing street address',
|
||||
},
|
||||
billingCity: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Billing city',
|
||||
},
|
||||
billingState: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Billing state/province',
|
||||
},
|
||||
billingPostalCode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Billing postal code',
|
||||
},
|
||||
billingCountry: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Billing country',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Account description',
|
||||
},
|
||||
annualRevenue: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Annual revenue (number)',
|
||||
},
|
||||
numberOfEmployees: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of employees (number)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
let instanceUrl = params.instanceUrl
|
||||
|
||||
if (!instanceUrl && params.idToken) {
|
||||
try {
|
||||
const base64Url = params.idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) {
|
||||
instanceUrl = match[1]
|
||||
}
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') {
|
||||
instanceUrl = match[1]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
|
||||
if (!instanceUrl) {
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Account`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
Name: params.name,
|
||||
}
|
||||
|
||||
if (params.type) body.Type = params.type
|
||||
if (params.industry) body.Industry = params.industry
|
||||
if (params.phone) body.Phone = params.phone
|
||||
if (params.website) body.Website = params.website
|
||||
if (params.billingStreet) body.BillingStreet = params.billingStreet
|
||||
if (params.billingCity) body.BillingCity = params.billingCity
|
||||
if (params.billingState) body.BillingState = params.billingState
|
||||
if (params.billingPostalCode) body.BillingPostalCode = params.billingPostalCode
|
||||
if (params.billingCountry) body.BillingCountry = params.billingCountry
|
||||
if (params.description) body.Description = params.description
|
||||
if (params.annualRevenue) body.AnnualRevenue = Number.parseFloat(params.annualRevenue)
|
||||
if (params.numberOfEmployees)
|
||||
body.NumberOfEmployees = Number.parseInt(params.numberOfEmployees)
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Salesforce API request failed', { data, status: response.status })
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to create account in Salesforce')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
success: data.success,
|
||||
created: true,
|
||||
metadata: {
|
||||
operation: 'create_account' as const,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created account data',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Created account ID' },
|
||||
success: { type: 'boolean', description: 'Salesforce operation success' },
|
||||
created: { type: 'boolean', description: 'Whether account was created' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
145
apps/sim/tools/salesforce/delete_account.ts
Normal file
145
apps/sim/tools/salesforce/delete_account.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceDeleteAccount')
|
||||
|
||||
export interface SalesforceDeleteAccountParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
accountId: string
|
||||
}
|
||||
|
||||
export interface SalesforceDeleteAccountResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
deleted: boolean
|
||||
metadata: {
|
||||
operation: 'delete_account'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const salesforceDeleteAccountTool: ToolConfig<
|
||||
SalesforceDeleteAccountParams,
|
||||
SalesforceDeleteAccountResponse
|
||||
> = {
|
||||
id: 'salesforce_delete_account',
|
||||
name: 'Delete Account from Salesforce',
|
||||
description: 'Delete an account from Salesforce CRM',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'salesforce',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
idToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
accountId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Account ID to delete (required)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
let instanceUrl = params.instanceUrl
|
||||
|
||||
if (!instanceUrl && params.idToken) {
|
||||
try {
|
||||
const base64Url = params.idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) {
|
||||
instanceUrl = match[1]
|
||||
}
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') {
|
||||
instanceUrl = match[1]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
|
||||
if (!instanceUrl) {
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Account/${params.accountId}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
logger.error('Salesforce API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
data[0]?.message || data.message || 'Failed to delete account from Salesforce'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: params?.accountId || '',
|
||||
deleted: true,
|
||||
metadata: {
|
||||
operation: 'delete_account' as const,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Deleted account data',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Deleted account ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether account was deleted' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
177
apps/sim/tools/salesforce/get_accounts.ts
Normal file
177
apps/sim/tools/salesforce/get_accounts.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
SalesforceGetAccountsParams,
|
||||
SalesforceGetAccountsResponse,
|
||||
} from '@/tools/salesforce/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceGetAccounts')
|
||||
|
||||
export const salesforceGetAccountsTool: ToolConfig<
|
||||
SalesforceGetAccountsParams,
|
||||
SalesforceGetAccountsResponse
|
||||
> = {
|
||||
id: 'salesforce_get_accounts',
|
||||
name: 'Get Accounts from Salesforce',
|
||||
description: 'Retrieve accounts from Salesforce CRM',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'salesforce',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Salesforce API',
|
||||
},
|
||||
idToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The ID token from Salesforce OAuth (contains instance URL)',
|
||||
},
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The Salesforce instance URL',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to return (default: 100, max: 2000)',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of fields to return (e.g., "Id,Name,Industry,Phone")',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Field to order by (e.g., "Name ASC" or "CreatedDate DESC")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
let instanceUrl = params.instanceUrl
|
||||
|
||||
if (!instanceUrl && params.idToken) {
|
||||
try {
|
||||
const base64Url = params.idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) {
|
||||
instanceUrl = match[1]
|
||||
}
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') {
|
||||
instanceUrl = match[1]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
|
||||
if (!instanceUrl) {
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
const limit = params.limit ? Number.parseInt(params.limit) : 100
|
||||
const fields =
|
||||
params.fields ||
|
||||
'Id,Name,Type,Industry,BillingCity,BillingState,BillingCountry,Phone,Website'
|
||||
const orderBy = params.orderBy || 'Name ASC'
|
||||
|
||||
// Build SOQL query
|
||||
const query = `SELECT ${fields} FROM Account ORDER BY ${orderBy} LIMIT ${limit}`
|
||||
const encodedQuery = encodeURIComponent(query)
|
||||
|
||||
return `${instanceUrl}/services/data/v59.0/query?q=${encodedQuery}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Salesforce API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
data[0]?.message || data.message || 'Failed to fetch accounts from Salesforce'
|
||||
)
|
||||
}
|
||||
|
||||
const accounts = data.records || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
accounts,
|
||||
paging: {
|
||||
nextRecordsUrl: data.nextRecordsUrl,
|
||||
totalSize: data.totalSize || accounts.length,
|
||||
done: data.done !== false,
|
||||
},
|
||||
metadata: {
|
||||
operation: 'get_accounts' as const,
|
||||
totalReturned: accounts.length,
|
||||
hasMore: !data.done,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Accounts data',
|
||||
properties: {
|
||||
accounts: {
|
||||
type: 'array',
|
||||
description: 'Array of account objects',
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination information',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
34
apps/sim/tools/salesforce/index.ts
Normal file
34
apps/sim/tools/salesforce/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export {
|
||||
salesforceCreateCaseTool,
|
||||
salesforceDeleteCaseTool,
|
||||
salesforceGetCasesTool,
|
||||
salesforceUpdateCaseTool,
|
||||
} from './cases'
|
||||
export {
|
||||
salesforceCreateContactTool,
|
||||
salesforceDeleteContactTool,
|
||||
salesforceGetContactsTool,
|
||||
salesforceUpdateContactTool,
|
||||
} from './contacts'
|
||||
export { salesforceCreateAccountTool } from './create_account'
|
||||
export { salesforceDeleteAccountTool } from './delete_account'
|
||||
export { salesforceGetAccountsTool } from './get_accounts'
|
||||
export {
|
||||
salesforceCreateLeadTool,
|
||||
salesforceDeleteLeadTool,
|
||||
salesforceGetLeadsTool,
|
||||
salesforceUpdateLeadTool,
|
||||
} from './leads'
|
||||
export {
|
||||
salesforceCreateOpportunityTool,
|
||||
salesforceDeleteOpportunityTool,
|
||||
salesforceGetOpportunitiesTool,
|
||||
salesforceUpdateOpportunityTool,
|
||||
} from './opportunities'
|
||||
export {
|
||||
salesforceCreateTaskTool,
|
||||
salesforceDeleteTaskTool,
|
||||
salesforceGetTasksTool,
|
||||
salesforceUpdateTaskTool,
|
||||
} from './tasks'
|
||||
export { salesforceUpdateAccountTool } from './update_account'
|
||||
351
apps/sim/tools/salesforce/leads.ts
Normal file
351
apps/sim/tools/salesforce/leads.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceLeads')
|
||||
|
||||
function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
|
||||
if (instanceUrl) return instanceUrl
|
||||
if (idToken) {
|
||||
try {
|
||||
const base64Url = idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) return match[1]
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') return match[1]
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
// Get Leads
|
||||
export interface SalesforceGetLeadsParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
leadId?: string
|
||||
limit?: string
|
||||
fields?: string
|
||||
orderBy?: string
|
||||
}
|
||||
|
||||
export const salesforceGetLeadsTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_get_leads',
|
||||
name: 'Get Leads from Salesforce',
|
||||
description: 'Get lead(s) from Salesforce',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
leadId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Lead ID (optional)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Max results (default: 100)',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated fields',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Order by field',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
if (params.leadId) {
|
||||
const fields =
|
||||
params.fields || 'Id,FirstName,LastName,Company,Email,Phone,Status,LeadSource'
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Lead/${params.leadId}?fields=${fields}`
|
||||
}
|
||||
const limit = params.limit ? Number.parseInt(params.limit) : 100
|
||||
const fields = params.fields || 'Id,FirstName,LastName,Company,Email,Phone,Status,LeadSource'
|
||||
const orderBy = params.orderBy || 'LastName ASC'
|
||||
const query = `SELECT ${fields} FROM Lead ORDER BY ${orderBy} LIMIT ${limit}`
|
||||
return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch leads')
|
||||
if (params.leadId) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
lead: data,
|
||||
metadata: { operation: 'get_leads', singleLead: true },
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
const leads = data.records || []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
leads,
|
||||
paging: {
|
||||
nextRecordsUrl: data.nextRecordsUrl,
|
||||
totalSize: data.totalSize || leads.length,
|
||||
done: data.done !== false,
|
||||
},
|
||||
metadata: { operation: 'get_leads', totalReturned: leads.length, hasMore: !data.done },
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success status' },
|
||||
output: { type: 'object', description: 'Lead data' },
|
||||
},
|
||||
}
|
||||
|
||||
// Create Lead
|
||||
export const salesforceCreateLeadTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_create_lead',
|
||||
name: 'Create Lead in Salesforce',
|
||||
description: 'Create a new lead',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Last name (required)',
|
||||
},
|
||||
company: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Company (required)',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'First name',
|
||||
},
|
||||
email: { type: 'string', required: false, visibility: 'user-only', description: 'Email' },
|
||||
phone: { type: 'string', required: false, visibility: 'user-only', description: 'Phone' },
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Lead status',
|
||||
},
|
||||
leadSource: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Lead source',
|
||||
},
|
||||
title: { type: 'string', required: false, visibility: 'user-only', description: 'Title' },
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { LastName: params.lastName, Company: params.company }
|
||||
if (params.firstName) body.FirstName = params.firstName
|
||||
if (params.email) body.Email = params.email
|
||||
if (params.phone) body.Phone = params.phone
|
||||
if (params.status) body.Status = params.status
|
||||
if (params.leadSource) body.LeadSource = params.leadSource
|
||||
if (params.title) body.Title = params.title
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create lead')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
success: data.success,
|
||||
created: true,
|
||||
metadata: { operation: 'create_lead' },
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Created lead' },
|
||||
},
|
||||
}
|
||||
|
||||
// Update Lead
|
||||
export const salesforceUpdateLeadTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_update_lead',
|
||||
name: 'Update Lead in Salesforce',
|
||||
description: 'Update an existing lead',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
leadId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Lead ID (required)',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Last name',
|
||||
},
|
||||
company: { type: 'string', required: false, visibility: 'user-only', description: 'Company' },
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'First name',
|
||||
},
|
||||
email: { type: 'string', required: false, visibility: 'user-only', description: 'Email' },
|
||||
phone: { type: 'string', required: false, visibility: 'user-only', description: 'Phone' },
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Lead status',
|
||||
},
|
||||
leadSource: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Lead source',
|
||||
},
|
||||
title: { type: 'string', required: false, visibility: 'user-only', description: 'Title' },
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead/${params.leadId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.lastName) body.LastName = params.lastName
|
||||
if (params.company) body.Company = params.company
|
||||
if (params.firstName) body.FirstName = params.firstName
|
||||
if (params.email) body.Email = params.email
|
||||
if (params.phone) body.Phone = params.phone
|
||||
if (params.status) body.Status = params.status
|
||||
if (params.leadSource) body.LeadSource = params.leadSource
|
||||
if (params.title) body.Title = params.title
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to update lead')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { id: params.leadId, updated: true, metadata: { operation: 'update_lead' } },
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Updated lead' },
|
||||
},
|
||||
}
|
||||
|
||||
// Delete Lead
|
||||
export const salesforceDeleteLeadTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_delete_lead',
|
||||
name: 'Delete Lead from Salesforce',
|
||||
description: 'Delete a lead',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
leadId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Lead ID (required)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead/${params.leadId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to delete lead')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { id: params.leadId, deleted: true, metadata: { operation: 'delete_lead' } },
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Deleted lead' },
|
||||
},
|
||||
}
|
||||
355
apps/sim/tools/salesforce/opportunities.ts
Normal file
355
apps/sim/tools/salesforce/opportunities.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceOpportunities')
|
||||
|
||||
function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
|
||||
if (instanceUrl) return instanceUrl
|
||||
if (idToken) {
|
||||
try {
|
||||
const base64Url = idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) return match[1]
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') return match[1]
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
// Get Opportunities
|
||||
export const salesforceGetOpportunitiesTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_get_opportunities',
|
||||
name: 'Get Opportunities from Salesforce',
|
||||
description: 'Get opportunity(ies) from Salesforce',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
opportunityId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Opportunity ID (optional)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Max results (default: 100)',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated fields',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Order by field',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
if (params.opportunityId) {
|
||||
const fields = params.fields || 'Id,Name,AccountId,Amount,StageName,CloseDate,Probability'
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}?fields=${fields}`
|
||||
}
|
||||
const limit = params.limit ? Number.parseInt(params.limit) : 100
|
||||
const fields = params.fields || 'Id,Name,AccountId,Amount,StageName,CloseDate,Probability'
|
||||
const orderBy = params.orderBy || 'CloseDate DESC'
|
||||
const query = `SELECT ${fields} FROM Opportunity ORDER BY ${orderBy} LIMIT ${limit}`
|
||||
return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok)
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to fetch opportunities')
|
||||
if (params.opportunityId) {
|
||||
return {
|
||||
success: true,
|
||||
output: { opportunity: data, metadata: { operation: 'get_opportunities' }, success: true },
|
||||
}
|
||||
}
|
||||
const opportunities = data.records || []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
opportunities,
|
||||
paging: {
|
||||
nextRecordsUrl: data.nextRecordsUrl,
|
||||
totalSize: data.totalSize || opportunities.length,
|
||||
done: data.done !== false,
|
||||
},
|
||||
metadata: {
|
||||
operation: 'get_opportunities',
|
||||
totalReturned: opportunities.length,
|
||||
hasMore: !data.done,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Opportunity data' },
|
||||
},
|
||||
}
|
||||
|
||||
// Create Opportunity
|
||||
export const salesforceCreateOpportunityTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_create_opportunity',
|
||||
name: 'Create Opportunity in Salesforce',
|
||||
description: 'Create a new opportunity',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Opportunity name (required)',
|
||||
},
|
||||
stageName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Stage name (required)',
|
||||
},
|
||||
closeDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Close date YYYY-MM-DD (required)',
|
||||
},
|
||||
accountId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Account ID',
|
||||
},
|
||||
amount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Amount (number)',
|
||||
},
|
||||
probability: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Probability (0-100)',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
Name: params.name,
|
||||
StageName: params.stageName,
|
||||
CloseDate: params.closeDate,
|
||||
}
|
||||
if (params.accountId) body.AccountId = params.accountId
|
||||
if (params.amount) body.Amount = Number.parseFloat(params.amount)
|
||||
if (params.probability) body.Probability = Number.parseInt(params.probability)
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok)
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to create opportunity')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
success: data.success,
|
||||
created: true,
|
||||
metadata: { operation: 'create_opportunity' },
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Created opportunity' },
|
||||
},
|
||||
}
|
||||
|
||||
// Update Opportunity
|
||||
export const salesforceUpdateOpportunityTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_update_opportunity',
|
||||
name: 'Update Opportunity in Salesforce',
|
||||
description: 'Update an existing opportunity',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
opportunityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Opportunity ID (required)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Opportunity name',
|
||||
},
|
||||
stageName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Stage name',
|
||||
},
|
||||
closeDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Close date YYYY-MM-DD',
|
||||
},
|
||||
accountId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Account ID',
|
||||
},
|
||||
amount: { type: 'string', required: false, visibility: 'user-only', description: 'Amount' },
|
||||
probability: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Probability (0-100)',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.name) body.Name = params.name
|
||||
if (params.stageName) body.StageName = params.stageName
|
||||
if (params.closeDate) body.CloseDate = params.closeDate
|
||||
if (params.accountId) body.AccountId = params.accountId
|
||||
if (params.amount) body.Amount = Number.parseFloat(params.amount)
|
||||
if (params.probability) body.Probability = Number.parseInt(params.probability)
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to update opportunity')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: params.opportunityId,
|
||||
updated: true,
|
||||
metadata: { operation: 'update_opportunity' },
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Updated opportunity' },
|
||||
},
|
||||
}
|
||||
|
||||
// Delete Opportunity
|
||||
export const salesforceDeleteOpportunityTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_delete_opportunity',
|
||||
name: 'Delete Opportunity from Salesforce',
|
||||
description: 'Delete an opportunity',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
opportunityId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Opportunity ID (required)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to delete opportunity')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: params.opportunityId,
|
||||
deleted: true,
|
||||
metadata: { operation: 'delete_opportunity' },
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Deleted opportunity' },
|
||||
},
|
||||
}
|
||||
321
apps/sim/tools/salesforce/tasks.ts
Normal file
321
apps/sim/tools/salesforce/tasks.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SalesforceTasks')
|
||||
|
||||
function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
|
||||
if (instanceUrl) return instanceUrl
|
||||
if (idToken) {
|
||||
try {
|
||||
const base64Url = idToken.split('.')[1]
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
|
||||
.join('')
|
||||
)
|
||||
const decoded = JSON.parse(jsonPayload)
|
||||
if (decoded.profile) {
|
||||
const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
|
||||
if (match) return match[1]
|
||||
} else if (decoded.sub) {
|
||||
const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
|
||||
if (match && match[1] !== 'https://login.salesforce.com') return match[1]
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to decode Salesforce idToken', { error })
|
||||
}
|
||||
}
|
||||
throw new Error('Salesforce instance URL is required but not provided')
|
||||
}
|
||||
|
||||
// Get Tasks
|
||||
export const salesforceGetTasksTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_get_tasks',
|
||||
name: 'Get Tasks from Salesforce',
|
||||
description: 'Get task(s) from Salesforce',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
taskId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Task ID (optional)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Max results (default: 100)',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated fields',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Order by field',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => {
|
||||
const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
|
||||
if (params.taskId) {
|
||||
const fields =
|
||||
params.fields || 'Id,Subject,Status,Priority,ActivityDate,WhoId,WhatId,OwnerId'
|
||||
return `${instanceUrl}/services/data/v59.0/sobjects/Task/${params.taskId}?fields=${fields}`
|
||||
}
|
||||
const limit = params.limit ? Number.parseInt(params.limit) : 100
|
||||
const fields = params.fields || 'Id,Subject,Status,Priority,ActivityDate,WhoId,WhatId,OwnerId'
|
||||
const orderBy = params.orderBy || 'ActivityDate DESC'
|
||||
const query = `SELECT ${fields} FROM Task ORDER BY ${orderBy} LIMIT ${limit}`
|
||||
return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch tasks')
|
||||
if (params.taskId) {
|
||||
return {
|
||||
success: true,
|
||||
output: { task: data, metadata: { operation: 'get_tasks' }, success: true },
|
||||
}
|
||||
}
|
||||
const tasks = data.records || []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tasks,
|
||||
paging: {
|
||||
nextRecordsUrl: data.nextRecordsUrl,
|
||||
totalSize: data.totalSize || tasks.length,
|
||||
done: data.done !== false,
|
||||
},
|
||||
metadata: { operation: 'get_tasks', totalReturned: tasks.length, hasMore: !data.done },
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Task data' },
|
||||
},
|
||||
}
|
||||
|
||||
// Create Task
|
||||
export const salesforceCreateTaskTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_create_task',
|
||||
name: 'Create Task in Salesforce',
|
||||
description: 'Create a new task',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Task subject (required)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Status (e.g., Not Started, In Progress, Completed)',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Priority (e.g., Low, Normal, High)',
|
||||
},
|
||||
activityDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Due date YYYY-MM-DD',
|
||||
},
|
||||
whoId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Related Contact/Lead ID',
|
||||
},
|
||||
whatId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Related Account/Opportunity ID',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { Subject: params.subject }
|
||||
if (params.status) body.Status = params.status
|
||||
if (params.priority) body.Priority = params.priority
|
||||
if (params.activityDate) body.ActivityDate = params.activityDate
|
||||
if (params.whoId) body.WhoId = params.whoId
|
||||
if (params.whatId) body.WhatId = params.whatId
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create task')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
success: data.success,
|
||||
created: true,
|
||||
metadata: { operation: 'create_task' },
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Created task' },
|
||||
},
|
||||
}
|
||||
|
||||
// Update Task
|
||||
export const salesforceUpdateTaskTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_update_task',
|
||||
name: 'Update Task in Salesforce',
|
||||
description: 'Update an existing task',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
taskId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Task ID (required)',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Task subject',
|
||||
},
|
||||
status: { type: 'string', required: false, visibility: 'user-only', description: 'Status' },
|
||||
priority: { type: 'string', required: false, visibility: 'user-only', description: 'Priority' },
|
||||
activityDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Due date YYYY-MM-DD',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task/${params.taskId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.subject) body.Subject = params.subject
|
||||
if (params.status) body.Status = params.status
|
||||
if (params.priority) body.Priority = params.priority
|
||||
if (params.activityDate) body.ActivityDate = params.activityDate
|
||||
if (params.description) body.Description = params.description
|
||||
return body
|
||||
},
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to update task')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { id: params.taskId, updated: true, metadata: { operation: 'update_task' } },
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Updated task' },
|
||||
},
|
||||
}
|
||||
|
||||
// Delete Task
|
||||
export const salesforceDeleteTaskTool: ToolConfig<any, any> = {
|
||||
id: 'salesforce_delete_task',
|
||||
name: 'Delete Task from Salesforce',
|
||||
description: 'Delete a task',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'salesforce' },
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
idToken: { type: 'string', required: false, visibility: 'hidden' },
|
||||
instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
|
||||
taskId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Task ID (required)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task/${params.taskId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
|
||||
},
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
throw new Error(data[0]?.message || data.message || 'Failed to delete task')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { id: params.taskId, deleted: true, metadata: { operation: 'delete_task' } },
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Success' },
|
||||
output: { type: 'object', description: 'Deleted task' },
|
||||
},
|
||||
}
|
||||
150
apps/sim/tools/salesforce/types.ts
Normal file
150
apps/sim/tools/salesforce/types.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// Common Salesforce types
|
||||
export interface SalesforceAccount {
|
||||
Id: string
|
||||
Name: string
|
||||
Type?: string
|
||||
Industry?: string
|
||||
BillingStreet?: string
|
||||
BillingCity?: string
|
||||
BillingState?: string
|
||||
BillingPostalCode?: string
|
||||
BillingCountry?: string
|
||||
Phone?: string
|
||||
Website?: string
|
||||
AnnualRevenue?: number
|
||||
NumberOfEmployees?: number
|
||||
Description?: string
|
||||
OwnerId?: string
|
||||
CreatedDate?: string
|
||||
LastModifiedDate?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface SalesforcePaging {
|
||||
nextRecordsUrl?: string
|
||||
totalSize: number
|
||||
done: boolean
|
||||
}
|
||||
|
||||
// Get Accounts
|
||||
export interface SalesforceGetAccountsResponse extends ToolResponse {
|
||||
output: {
|
||||
accounts: SalesforceAccount[]
|
||||
paging?: SalesforcePaging
|
||||
metadata: {
|
||||
operation: 'get_accounts'
|
||||
totalReturned: number
|
||||
hasMore: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SalesforceGetAccountsParams {
|
||||
accessToken: string
|
||||
idToken?: string
|
||||
instanceUrl?: string
|
||||
limit?: string
|
||||
fields?: string
|
||||
orderBy?: string
|
||||
}
|
||||
|
||||
// Create Account
|
||||
export interface SalesforceCreateAccountResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
success: boolean
|
||||
created: boolean
|
||||
metadata: {
|
||||
operation: 'create_account'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update Account
|
||||
export interface SalesforceUpdateAccountResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
updated: boolean
|
||||
metadata: {
|
||||
operation: 'update_account'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Account
|
||||
export interface SalesforceDeleteAccountResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
deleted: boolean
|
||||
metadata: {
|
||||
operation: 'delete_account'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Contact types
|
||||
export interface SalesforceGetContactsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
contacts?: any[]
|
||||
contact?: any
|
||||
paging?: {
|
||||
nextRecordsUrl?: string
|
||||
totalSize: number
|
||||
done: boolean
|
||||
}
|
||||
metadata: {
|
||||
operation: 'get_contacts'
|
||||
totalReturned?: number
|
||||
hasMore?: boolean
|
||||
singleContact?: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SalesforceCreateContactResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
success: boolean
|
||||
created: boolean
|
||||
metadata: { operation: 'create_contact' }
|
||||
}
|
||||
}
|
||||
|
||||
export interface SalesforceUpdateContactResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
updated: boolean
|
||||
metadata: { operation: 'update_contact' }
|
||||
}
|
||||
}
|
||||
|
||||
export interface SalesforceDeleteContactResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
deleted: boolean
|
||||
metadata: { operation: 'delete_contact' }
|
||||
}
|
||||
}
|
||||
|
||||
// Generic Salesforce response type for the block
|
||||
export type SalesforceResponse =
|
||||
| SalesforceGetAccountsResponse
|
||||
| SalesforceCreateAccountResponse
|
||||
| SalesforceUpdateAccountResponse
|
||||
| SalesforceDeleteAccountResponse
|
||||
| SalesforceGetContactsResponse
|
||||
| SalesforceCreateContactResponse
|
||||
| SalesforceUpdateContactResponse
|
||||
| SalesforceDeleteContactResponse
|
||||
| { success: boolean; output: any } // Generic for leads, opportunities, cases, tasks
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user