diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 0cf5773ca48..086e42c494f 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4186,6 +4186,17 @@ export function IntercomIcon(props: SVGProps) { ) } +export function IroncladIcon(props: SVGProps) { + return ( + + + + ) +} + export function LoopsIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 900229eaad7..88bd21aea8f 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -84,6 +84,7 @@ import { IncidentioIcon, InfisicalIcon, IntercomIcon, + IroncladIcon, JinaAIIcon, JiraIcon, JiraServiceManagementIcon, @@ -258,6 +259,7 @@ export const blockTypeToIconMap: Record = { incidentio: IncidentioIcon, infisical: InfisicalIcon, intercom_v2: IntercomIcon, + ironclad: IroncladIcon, jina: JinaAIIcon, jira: JiraIcon, jira_service_management: JiraServiceManagementIcon, diff --git a/apps/docs/content/docs/en/tools/ironclad.mdx b/apps/docs/content/docs/en/tools/ironclad.mdx new file mode 100644 index 00000000000..e5469c2942b --- /dev/null +++ b/apps/docs/content/docs/en/tools/ironclad.mdx @@ -0,0 +1,259 @@ +--- +title: Ironclad +description: Contract lifecycle management with Ironclad +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Ironclad](https://ironcladapp.com/) is a leading contract lifecycle management (CLM) platform that helps legal teams streamline contract creation, negotiation, approval, and storage. It enables organizations to manage the entire contract process from start to finish in a single, unified platform. + +With Ironclad, you can: + +- **Automate contract workflows**: Create and manage contract workflows using configurable templates with built-in approval routing +- **Manage records**: Store and organize contract records with custom metadata, properties, and linked relationships +- **Track approvals**: Monitor approval status across workflows with conditional approval groups +- **Collaborate with comments**: Add and view comments on workflows for team communication and audit trails + +In Sim, the Ironclad integration enables your agents to interact with Ironclad's workflow and records APIs programmatically. This allows for seamless contract operations like creating new workflows from templates, updating contract metadata, managing records, and tracking approvals - all within your agent workflows. Use Ironclad to automate contract lifecycle processes, keep records in sync, and enable your agents to participate in contract management decisions. +{/* MANUAL-CONTENT-END */} + +## Usage Instructions + +Manage workflows and records in Ironclad. Create and track contract workflows, manage records, view approvals, add comments, and update metadata. Requires an Ironclad OAuth connection. + + + +## Tools + +### `ironclad_create_workflow` + +Create a new workflow in Ironclad using a specified template and attributes. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `template` | string | Yes | The template ID to use for the workflow | +| `attributes` | string | No | JSON object of workflow attributes | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The ID of the created workflow | +| `status` | string | The status of the workflow | +| `template` | string | The template used for the workflow | +| `creator` | string | The creator of the workflow | + +### `ironclad_list_workflows` + +List all workflows in Ironclad with pagination support. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `page` | number | No | Page number \(starting from 0\) | +| `perPage` | number | No | Number of results per page \(max 100\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `workflows` | json | List of workflows | +| `page` | number | Current page number | +| `pageSize` | number | Number of results per page | +| `count` | number | Total number of workflows | + +### `ironclad_get_workflow` + +Retrieve details of a specific workflow by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The workflow ID | +| `status` | string | The workflow status | +| `template` | string | The template used for the workflow | +| `creator` | string | The creator of the workflow | +| `step` | string | The current step of the workflow | +| `attributes` | json | The workflow attributes | + +### `ironclad_update_workflow_metadata` + +Update attributes on a workflow. The workflow must be in the Review step. Supports set and remove actions. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | +| `actions` | string | Yes | JSON array of actions. Each action has "action" \(set/remove\), "field", and optionally "value". | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the update was successful | + +### `ironclad_cancel_workflow` + +Cancel a workflow instance by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow to cancel | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the cancellation was successful | + +### `ironclad_list_workflow_approvals` + +List all triggered approvals for a specific workflow. Conditional approvals that have not been triggered will not appear. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `approvals` | json | List of triggered approval groups for the workflow | + +### `ironclad_add_comment` + +Add a comment to a workflow. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | +| `comment` | string | Yes | The comment text to add | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the comment was added successfully | + +### `ironclad_list_workflow_comments` + +List all comments on a workflow. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `comments` | json | List of comments on the workflow | + +### `ironclad_create_record` + +Create a new record in Ironclad with a specified type, name, and properties. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recordType` | string | Yes | The record type \(e.g., "contract", "Statement of Work"\) | +| `name` | string | Yes | A human-readable name for the record | +| `properties` | string | No | JSON object of properties. Each property has a "type" \(string/number/email/date/monetary_amount\) and "value". | +| `links` | string | No | JSON array of linked record objects, each with a "recordId" field | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The ID of the created record | +| `name` | string | The name of the record | +| `type` | string | The type of the record | + +### `ironclad_list_records` + +List all records in Ironclad with pagination and optional filtering by last updated time. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `page` | number | No | Page number \(starting from 0\) | +| `pageSize` | number | No | Number of results per page \(max 100\) | +| `lastUpdated` | string | No | Filter records updated on or after this ISO 8601 timestamp | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `records` | json | List of records | +| `page` | number | Current page number | +| `pageSize` | number | Number of results per page | +| `count` | number | Total number of records | + +### `ironclad_get_record` + +Retrieve details of a specific record by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recordId` | string | Yes | The unique identifier of the record | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The record ID | +| `name` | string | The record name | +| `type` | string | The record type | +| `properties` | json | The record properties | +| `createdAt` | string | When the record was created | +| `updatedAt` | string | When the record was last updated | + +### `ironclad_update_record` + +Update metadata fields on an existing record. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recordId` | string | Yes | The unique identifier of the record to update | +| `properties` | string | Yes | JSON object of fields to update \(e.g., \{"fieldName": "newValue"\}\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The record ID | +| `name` | string | The record name | +| `type` | string | The record type | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 9ff8f3ff78a..4a25e3514a7 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -79,6 +79,7 @@ "incidentio", "infisical", "intercom", + "ironclad", "jina", "jira", "jira_service_management", diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 7580b713c57..8036edba919 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -84,6 +84,7 @@ import { IncidentioIcon, InfisicalIcon, IntercomIcon, + IroncladIcon, JinaAIIcon, JiraIcon, JiraServiceManagementIcon, @@ -258,6 +259,7 @@ export const blockTypeToIconMap: Record = { incidentio: IncidentioIcon, infisical: InfisicalIcon, intercom_v2: IntercomIcon, + ironclad: IroncladIcon, jina: JinaAIIcon, jira: JiraIcon, jira_service_management: JiraServiceManagementIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 7d8cf706be5..9adc405d1e6 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -5758,6 +5758,73 @@ "integrationType": "customer-support", "tags": ["customer-support", "messaging"] }, + { + "type": "ironclad", + "slug": "ironclad", + "name": "Ironclad", + "description": "Contract lifecycle management with Ironclad", + "longDescription": "Manage workflows and records in Ironclad. Create and track contract workflows, manage records, view approvals, add comments, and update metadata. Requires an Ironclad OAuth connection.", + "bgColor": "#FFFFFF", + "iconName": "IroncladIcon", + "docsUrl": "https://docs.sim.ai/tools/ironclad", + "operations": [ + { + "name": "Create Workflow", + "description": "Create a new workflow in Ironclad using a specified template and attributes." + }, + { + "name": "List Workflows", + "description": "List all workflows in Ironclad with pagination support." + }, + { + "name": "Get Workflow", + "description": "Retrieve details of a specific workflow by its ID." + }, + { + "name": "Update Workflow Metadata", + "description": "Update attributes on a workflow. The workflow must be in the Review step. Supports set and remove actions." + }, + { + "name": "Cancel Workflow", + "description": "Cancel a workflow instance by its ID." + }, + { + "name": "List Workflow Approvals", + "description": "List all triggered approvals for a specific workflow. Conditional approvals that have not been triggered will not appear." + }, + { + "name": "Add Comment", + "description": "Add a comment to a workflow." + }, + { + "name": "List Workflow Comments", + "description": "List all comments on a workflow." + }, + { + "name": "Create Record", + "description": "Create a new record in Ironclad with a specified type, name, and properties." + }, + { + "name": "List Records", + "description": "List all records in Ironclad with pagination and optional filtering by last updated time." + }, + { + "name": "Get Record", + "description": "Retrieve details of a specific record by its ID." + }, + { + "name": "Update Record", + "description": "Update metadata fields on an existing record." + } + ], + "operationCount": 12, + "triggers": [], + "triggerCount": 0, + "authType": "oauth", + "category": "tools", + "integrationType": "documents", + "tags": ["e-signatures", "document-processing"] + }, { "type": "jina", "slug": "jina", diff --git a/apps/sim/blocks/blocks/ironclad.ts b/apps/sim/blocks/blocks/ironclad.ts new file mode 100644 index 00000000000..b0feff5ca35 --- /dev/null +++ b/apps/sim/blocks/blocks/ironclad.ts @@ -0,0 +1,309 @@ +import { IroncladIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +export const IroncladBlock: BlockConfig = { + type: 'ironclad', + name: 'Ironclad', + description: 'Contract lifecycle management with Ironclad', + longDescription: + 'Manage workflows and records in Ironclad. Create and track contract workflows, manage records, view approvals, add comments, and update metadata. Requires an Ironclad OAuth connection.', + docsLink: 'https://docs.sim.ai/tools/ironclad', + category: 'tools', + integrationType: IntegrationType.Documents, + tags: ['e-signatures', 'document-processing'], + bgColor: '#FFFFFF', + icon: IroncladIcon, + authMode: AuthMode.OAuth, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Workflow', id: 'create_workflow' }, + { label: 'List Workflows', id: 'list_workflows' }, + { label: 'Get Workflow', id: 'get_workflow' }, + { label: 'Update Workflow Metadata', id: 'update_workflow_metadata' }, + { label: 'Cancel Workflow', id: 'cancel_workflow' }, + { label: 'List Workflow Approvals', id: 'list_workflow_approvals' }, + { label: 'Add Comment', id: 'add_comment' }, + { label: 'List Workflow Comments', id: 'list_workflow_comments' }, + { label: 'Create Record', id: 'create_record' }, + { label: 'List Records', id: 'list_records' }, + { label: 'Get Record', id: 'get_record' }, + { label: 'Update Record', id: 'update_record' }, + ], + value: () => 'list_workflows', + }, + { + id: 'credential', + title: 'Ironclad Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + mode: 'basic', + serviceId: 'ironclad', + requiredScopes: getScopesForService('ironclad'), + placeholder: 'Select Ironclad account', + required: true, + }, + { + id: 'manualCredential', + title: 'Ironclad Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + + // Create Workflow fields + { + id: 'template', + title: 'Template ID', + type: 'short-input', + placeholder: 'Enter workflow template ID', + condition: { field: 'operation', value: 'create_workflow' }, + required: { field: 'operation', value: 'create_workflow' }, + }, + { + id: 'attributes', + title: 'Attributes', + type: 'long-input', + placeholder: '{"counterpartyName": "Acme Corp"}', + condition: { field: 'operation', value: 'create_workflow' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object of Ironclad workflow attributes based on the description. Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the workflow attributes...', + generationType: 'json-object', + }, + }, + + // Workflow ID field (shared by get, update metadata, cancel, approvals, comments) + { + id: 'workflowId', + title: 'Workflow ID', + type: 'short-input', + placeholder: 'Enter workflow ID', + condition: { + field: 'operation', + value: [ + 'get_workflow', + 'update_workflow_metadata', + 'cancel_workflow', + 'list_workflow_approvals', + 'add_comment', + 'list_workflow_comments', + ], + }, + required: { + field: 'operation', + value: [ + 'get_workflow', + 'update_workflow_metadata', + 'cancel_workflow', + 'list_workflow_approvals', + 'add_comment', + 'list_workflow_comments', + ], + }, + }, + + // Update Workflow Metadata fields + { + id: 'actions', + title: 'Actions', + type: 'long-input', + placeholder: '[{"action": "set", "field": "status", "value": "Approved"}]', + condition: { field: 'operation', value: 'update_workflow_metadata' }, + required: { field: 'operation', value: 'update_workflow_metadata' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of Ironclad workflow update actions. Each action has "action" ("set" or "remove"), "field" (attribute name), and optionally "value". Return ONLY the JSON array - no explanations, no extra text.', + placeholder: 'Describe the updates (e.g., "set status to Approved")...', + generationType: 'json-object', + }, + }, + + // Add Comment field + { + id: 'comment', + title: 'Comment', + type: 'long-input', + placeholder: 'Enter comment text', + condition: { field: 'operation', value: 'add_comment' }, + required: { field: 'operation', value: 'add_comment' }, + }, + + // Create Record fields + { + id: 'recordType', + title: 'Record Type', + type: 'short-input', + placeholder: 'e.g., contract, Statement of Work', + condition: { field: 'operation', value: 'create_record' }, + required: { field: 'operation', value: 'create_record' }, + }, + { + id: 'name', + title: 'Record Name', + type: 'short-input', + placeholder: 'Enter record name', + condition: { field: 'operation', value: 'create_record' }, + required: { field: 'operation', value: 'create_record' }, + }, + { + id: 'properties', + title: 'Properties', + type: 'long-input', + placeholder: '{"counterpartyName": {"type": "string", "value": "Acme Corp"}}', + condition: { field: 'operation', value: ['create_record', 'update_record'] }, + required: { field: 'operation', value: 'update_record' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object of Ironclad record properties. Each property has a "type" (string/number/email/date/monetary_amount) and "value". Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the record properties...', + generationType: 'json-object', + }, + }, + { + id: 'links', + title: 'Linked Records', + type: 'long-input', + placeholder: '[{"recordId": "abc-123"}]', + condition: { field: 'operation', value: 'create_record' }, + mode: 'advanced', + }, + + // Get/Update Record fields + { + id: 'recordId', + title: 'Record ID', + type: 'short-input', + placeholder: 'Enter record ID', + condition: { field: 'operation', value: ['get_record', 'update_record'] }, + required: { field: 'operation', value: ['get_record', 'update_record'] }, + }, + + // List pagination fields + { + id: 'page', + title: 'Page', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: ['list_workflows', 'list_records'] }, + mode: 'advanced', + }, + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: ['list_workflows', 'list_records'] }, + mode: 'advanced', + }, + { + id: 'lastUpdated', + title: 'Last Updated After', + type: 'short-input', + placeholder: '2024-01-01T00:00:00Z', + condition: { field: 'operation', value: 'list_records' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate an ISO 8601 timestamp based on the description. Return ONLY the timestamp string - no explanations, no extra text.', + placeholder: 'Describe the date (e.g., "yesterday", "last week")...', + generationType: 'timestamp', + }, + }, + ], + tools: { + access: [ + 'ironclad_create_workflow', + 'ironclad_list_workflows', + 'ironclad_get_workflow', + 'ironclad_update_workflow_metadata', + 'ironclad_cancel_workflow', + 'ironclad_list_workflow_approvals', + 'ironclad_add_comment', + 'ironclad_list_workflow_comments', + 'ironclad_create_record', + 'ironclad_list_records', + 'ironclad_get_record', + 'ironclad_update_record', + ], + config: { + tool: (params) => `ironclad_${params.operation}`, + params: (params) => { + const { oauthCredential, ...rest } = params + const result: Record = { + credential: oauthCredential, + ...rest, + } + if (result.workflowId !== undefined) { + result.ironcladWorkflowId = result.workflowId + result.workflowId = undefined + } + if (params.page !== undefined) result.page = Number(params.page) + if (params.pageSize !== undefined && params.pageSize !== '') { + if (params.operation === 'list_workflows') { + result.perPage = Number(params.pageSize) + result.pageSize = undefined + } else { + result.pageSize = Number(params.pageSize) + } + } + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Ironclad OAuth credential' }, + template: { type: 'string', description: 'Workflow template ID' }, + attributes: { type: 'string', description: 'Workflow attributes (JSON)' }, + workflowId: { type: 'string', description: 'Workflow ID' }, + actions: { type: 'string', description: 'Update actions (JSON array)' }, + comment: { type: 'string', description: 'Comment text' }, + recordType: { type: 'string', description: 'Record type' }, + name: { type: 'string', description: 'Record name' }, + properties: { type: 'string', description: 'Record properties (JSON)' }, + links: { type: 'string', description: 'Linked records (JSON array)' }, + recordId: { type: 'string', description: 'Record ID' }, + page: { type: 'number', description: 'Page number' }, + pageSize: { type: 'number', description: 'Results per page' }, + lastUpdated: { type: 'string', description: 'Filter by last updated timestamp' }, + }, + outputs: { + // Workflow outputs + id: { type: 'string', description: 'Resource ID' }, + status: { type: 'string', description: 'Workflow status' }, + template: { type: 'string', description: 'Workflow template' }, + creator: { type: 'string', description: 'Workflow creator' }, + step: { type: 'string', description: 'Current workflow step' }, + attributes: { type: 'json', description: 'Workflow attributes' }, + approvals: { type: 'json', description: 'Workflow approval groups' }, + comments: { type: 'json', description: 'Workflow comments' }, + // Record outputs + name: { type: 'string', description: 'Record name' }, + type: { type: 'string', description: 'Record type' }, + properties: { type: 'json', description: 'Record properties' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + updatedAt: { type: 'string', description: 'Last update timestamp' }, + // List outputs + workflows: { type: 'json', description: 'List of workflows' }, + records: { type: 'json', description: 'List of records' }, + page: { type: 'number', description: 'Current page number' }, + pageSize: { type: 'number', description: 'Results per page' }, + count: { type: 'number', description: 'Total count' }, + // Action outputs + success: { type: 'boolean', description: 'Whether the operation succeeded' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 15363a8ad53..bc8c70d903d 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -87,6 +87,7 @@ import { IncidentioBlock } from '@/blocks/blocks/incidentio' import { InfisicalBlock } from '@/blocks/blocks/infisical' import { InputTriggerBlock } from '@/blocks/blocks/input_trigger' import { IntercomBlock, IntercomV2Block } from '@/blocks/blocks/intercom' +import { IroncladBlock } from '@/blocks/blocks/ironclad' import { JinaBlock } from '@/blocks/blocks/jina' import { JiraBlock } from '@/blocks/blocks/jira' import { JiraServiceManagementBlock } from '@/blocks/blocks/jira_service_management' @@ -307,6 +308,7 @@ export const registry: Record = { input_trigger: InputTriggerBlock, intercom: IntercomBlock, intercom_v2: IntercomV2Block, + ironclad: IroncladBlock, jina: JinaBlock, jira: JiraBlock, jira_service_management: JiraServiceManagementBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 0cf5773ca48..086e42c494f 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4186,6 +4186,17 @@ export function IntercomIcon(props: SVGProps) { ) } +export function IroncladIcon(props: SVGProps) { + return ( + + + + ) +} + export function LoopsIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 7bfaa64889d..16253c55396 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -539,6 +539,7 @@ export const auth = betterAuth({ 'sharepoint', 'jira', 'airtable', + 'ironclad', 'box', 'dropbox', 'salesforce', @@ -1883,6 +1884,55 @@ export const auth = betterAuth({ }, }, + // Ironclad provider + { + providerId: 'ironclad', + clientId: env.IRONCLAD_CLIENT_ID as string, + clientSecret: env.IRONCLAD_CLIENT_SECRET as string, + authorizationUrl: 'https://na1.ironcladapp.com/oauth/authorize', + tokenUrl: 'https://na1.ironcladapp.com/oauth/token', + userInfoUrl: 'https://na1.ironcladapp.com/oauth/userinfo', + scopes: getCanonicalScopesForProvider('ironclad'), + responseType: 'code', + pkce: false, + accessType: 'offline', + prompt: 'consent', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/ironclad`, + getUserInfo: async (tokens) => { + try { + const response = await fetch('https://na1.ironcladapp.com/oauth/userinfo', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + + if (!response.ok) { + await response.text().catch(() => {}) + logger.error('Error fetching Ironclad user info:', { + status: response.status, + statusText: response.statusText, + }) + return null + } + + const data = await response.json() + const now = new Date() + + return { + id: `${data.id.toString()}-${crypto.randomUUID()}`, + name: data.username || data.email?.split('@')[0] || 'Ironclad User', + email: data.email || `${data.id}@ironclad.user`, + emailVerified: !!data.email, + createdAt: now, + updatedAt: now, + } + } catch (error) { + logger.error('Error in Ironclad getUserInfo:', { error }) + return null + } + }, + }, + // Notion provider { providerId: 'notion', diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 65ac812ec86..c340ec3c6d9 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -258,6 +258,8 @@ export const env = createEnv({ 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 + IRONCLAD_CLIENT_ID: z.string().optional(), // Ironclad OAuth client ID + IRONCLAD_CLIENT_SECRET: z.string().optional(), // Ironclad OAuth client secret APOLLO_API_KEY: z.string().optional(), // Apollo API key (optional system-wide config) SUPABASE_CLIENT_ID: z.string().optional(), // Supabase OAuth client ID SUPABASE_CLIENT_SECRET: z.string().optional(), // Supabase OAuth client secret diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 31f7d290fc4..61aee575337 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -23,6 +23,7 @@ import { GoogleSheetsIcon, GoogleTasksIcon, HubspotIcon, + IroncladIcon, JiraIcon, LinearIcon, LinkedInIcon, @@ -562,6 +563,32 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'airtable', }, + ironclad: { + name: 'Ironclad', + icon: IroncladIcon, + services: { + ironclad: { + name: 'Ironclad', + description: 'Manage contracts, workflows, and records in Ironclad.', + providerId: 'ironclad', + icon: IroncladIcon, + baseProviderIcon: IroncladIcon, + scopes: [ + 'public.workflows.readWorkflows', + 'public.workflows.createWorkflows', + 'public.workflows.updateWorkflows', + 'public.workflows.cancel', + 'public.workflows.readApprovals', + 'public.workflows.readComments', + 'public.workflows.createComments', + 'public.records.readRecords', + 'public.records.createRecords', + 'public.records.updateRecords', + ], + }, + }, + defaultService: 'ironclad', + }, notion: { name: 'Notion', icon: NotionIcon, @@ -1104,6 +1131,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { supportsRefreshTokenRotation: true, } } + case 'ironclad': { + const { clientId, clientSecret } = getCredentials( + env.IRONCLAD_CLIENT_ID, + env.IRONCLAD_CLIENT_SECRET + ) + return { + tokenEndpoint: 'https://na1.ironcladapp.com/oauth/token', + clientId, + clientSecret, + useBasicAuth: false, + supportsRefreshTokenRotation: true, + } + } case 'notion': { const { clientId, clientSecret } = getCredentials( env.NOTION_CLIENT_ID, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index eab51d7d34b..599555dbb45 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -42,6 +42,7 @@ export type OAuthProvider = | 'attio' | 'pipedrive' | 'hubspot' + | 'ironclad' | 'salesforce' | 'linkedin' | 'shopify' @@ -92,6 +93,7 @@ export type OAuthService = | 'attio' | 'pipedrive' | 'hubspot' + | 'ironclad' | 'salesforce' | 'linkedin' | 'shopify' diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index 60377544ecc..fb1077c16f7 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -127,6 +127,18 @@ export const SCOPE_DESCRIPTIONS: Record = { 'schema.bases:read': 'View bases and tables', 'webhook:manage': 'Manage webhooks', + // Ironclad scopes + 'public.workflows.readWorkflows': 'Read workflows', + 'public.workflows.createWorkflows': 'Create workflows', + 'public.workflows.updateWorkflows': 'Update workflows', + 'public.workflows.cancel': 'Cancel workflows', + 'public.workflows.readApprovals': 'Read workflow approvals', + 'public.workflows.readComments': 'Read workflow comments', + 'public.workflows.createComments': 'Create workflow comments', + 'public.records.readRecords': 'Read records', + 'public.records.createRecords': 'Create records', + 'public.records.updateRecords': 'Update records', + // Jira scopes 'read:jira-user': 'Read Jira user', 'read:jira-work': 'Read Jira work', diff --git a/apps/sim/tools/ironclad/add_comment.ts b/apps/sim/tools/ironclad/add_comment.ts new file mode 100644 index 00000000000..fcb4e91cd2f --- /dev/null +++ b/apps/sim/tools/ironclad/add_comment.ts @@ -0,0 +1,71 @@ +import type { IroncladAddCommentParams, IroncladAddCommentResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const addCommentTool: ToolConfig = { + id: 'ironclad_add_comment', + name: 'Ironclad Add Comment', + description: 'Add a comment to a workflow.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + ironcladWorkflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + comment: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The comment text to add', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/comments`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ + comment: params.comment, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json().catch(() => ({})) + throw new Error( + (data as Record).message || + (data as Record).error || + 'Failed to add comment' + ) + } + + return { + success: true, + output: { + success: true, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the comment was added successfully' }, + }, +} diff --git a/apps/sim/tools/ironclad/cancel_workflow.ts b/apps/sim/tools/ironclad/cancel_workflow.ts new file mode 100644 index 00000000000..34127d80510 --- /dev/null +++ b/apps/sim/tools/ironclad/cancel_workflow.ts @@ -0,0 +1,68 @@ +import type { + IroncladCancelWorkflowParams, + IroncladCancelWorkflowResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const cancelWorkflowTool: ToolConfig< + IroncladCancelWorkflowParams, + IroncladCancelWorkflowResponse +> = { + id: 'ironclad_cancel_workflow', + name: 'Ironclad Cancel Workflow', + description: 'Cancel a workflow instance by its ID.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + ironcladWorkflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow to cancel', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/cancel`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json().catch(() => ({})) + throw new Error( + (data as Record).message || + (data as Record).error || + 'Failed to cancel workflow' + ) + } + + return { + success: true, + output: { + success: true, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the cancellation was successful' }, + }, +} diff --git a/apps/sim/tools/ironclad/create_record.ts b/apps/sim/tools/ironclad/create_record.ts new file mode 100644 index 00000000000..77975c12a22 --- /dev/null +++ b/apps/sim/tools/ironclad/create_record.ts @@ -0,0 +1,108 @@ +import type { + IroncladCreateRecordParams, + IroncladCreateRecordResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const createRecordTool: ToolConfig< + IroncladCreateRecordParams, + IroncladCreateRecordResponse +> = { + id: 'ironclad_create_record', + name: 'Ironclad Create Record', + description: 'Create a new record in Ironclad with a specified type, name, and properties.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + recordType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The record type (e.g., "contract", "Statement of Work")', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'A human-readable name for the record', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'JSON object of properties. Each property has a "type" (string/number/email/date/monetary_amount) and "value".', + }, + links: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of linked record objects, each with a "recordId" field', + }, + }, + + request: { + url: () => 'https://na1.ironcladapp.com/public/api/v1/records', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { + type: params.recordType, + name: params.name, + } + if (params.properties) { + try { + body.properties = JSON.parse(params.properties) + } catch { + throw new Error('Invalid JSON in properties field') + } + } + if (params.links) { + try { + body.links = JSON.parse(params.links) + } catch { + throw new Error('Invalid JSON in links field') + } + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to create record') + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? '', + type: data.type ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The ID of the created record' }, + name: { type: 'string', description: 'The name of the record' }, + type: { type: 'string', description: 'The type of the record' }, + }, +} diff --git a/apps/sim/tools/ironclad/create_workflow.ts b/apps/sim/tools/ironclad/create_workflow.ts new file mode 100644 index 00000000000..cec3cc314fb --- /dev/null +++ b/apps/sim/tools/ironclad/create_workflow.ts @@ -0,0 +1,89 @@ +import type { + IroncladCreateWorkflowParams, + IroncladCreateWorkflowResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const createWorkflowTool: ToolConfig< + IroncladCreateWorkflowParams, + IroncladCreateWorkflowResponse +> = { + id: 'ironclad_create_workflow', + name: 'Ironclad Create Workflow', + description: 'Create a new workflow in Ironclad using a specified template and attributes.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + template: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The template ID to use for the workflow', + }, + attributes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON object of workflow attributes', + }, + }, + + request: { + url: () => 'https://na1.ironcladapp.com/public/api/v1/workflows', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { + template: params.template, + } + if (params.attributes) { + try { + body.attributes = JSON.parse(params.attributes) + } catch { + throw new Error('Invalid JSON in attributes field') + } + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to create workflow') + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? null, + template: data.template ?? null, + creator: data.creator ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The ID of the created workflow' }, + status: { type: 'string', description: 'The status of the workflow' }, + template: { type: 'string', description: 'The template used for the workflow' }, + creator: { type: 'string', description: 'The creator of the workflow' }, + }, +} diff --git a/apps/sim/tools/ironclad/get_record.ts b/apps/sim/tools/ironclad/get_record.ts new file mode 100644 index 00000000000..f7524ae9178 --- /dev/null +++ b/apps/sim/tools/ironclad/get_record.ts @@ -0,0 +1,67 @@ +import type { IroncladGetRecordParams, IroncladGetRecordResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const getRecordTool: ToolConfig = { + id: 'ironclad_get_record', + name: 'Ironclad Get Record', + description: 'Retrieve details of a specific record by its ID.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + recordId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the record', + }, + }, + + request: { + url: (params) => `https://na1.ironcladapp.com/public/api/v1/records/${params.recordId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get record') + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? null, + type: data.type ?? null, + properties: data.properties ?? null, + createdAt: data.createdAt ?? null, + updatedAt: data.updatedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The record ID' }, + name: { type: 'string', description: 'The record name' }, + type: { type: 'string', description: 'The record type' }, + properties: { type: 'json', description: 'The record properties' }, + createdAt: { type: 'string', description: 'When the record was created' }, + updatedAt: { type: 'string', description: 'When the record was last updated' }, + }, +} diff --git a/apps/sim/tools/ironclad/get_workflow.ts b/apps/sim/tools/ironclad/get_workflow.ts new file mode 100644 index 00000000000..9433498111f --- /dev/null +++ b/apps/sim/tools/ironclad/get_workflow.ts @@ -0,0 +1,68 @@ +import type { IroncladGetWorkflowParams, IroncladGetWorkflowResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const getWorkflowTool: ToolConfig = { + id: 'ironclad_get_workflow', + name: 'Ironclad Get Workflow', + description: 'Retrieve details of a specific workflow by its ID.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + ironcladWorkflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get workflow') + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? null, + template: data.template ?? null, + creator: data.creator ?? null, + step: data.step ?? null, + attributes: data.attributes ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The workflow ID' }, + status: { type: 'string', description: 'The workflow status' }, + template: { type: 'string', description: 'The template used for the workflow' }, + creator: { type: 'string', description: 'The creator of the workflow' }, + step: { type: 'string', description: 'The current step of the workflow' }, + attributes: { type: 'json', description: 'The workflow attributes' }, + }, +} diff --git a/apps/sim/tools/ironclad/index.ts b/apps/sim/tools/ironclad/index.ts new file mode 100644 index 00000000000..10be8772a20 --- /dev/null +++ b/apps/sim/tools/ironclad/index.ts @@ -0,0 +1,27 @@ +import { addCommentTool } from '@/tools/ironclad/add_comment' +import { cancelWorkflowTool } from '@/tools/ironclad/cancel_workflow' +import { createRecordTool } from '@/tools/ironclad/create_record' +import { createWorkflowTool } from '@/tools/ironclad/create_workflow' +import { getRecordTool } from '@/tools/ironclad/get_record' +import { getWorkflowTool } from '@/tools/ironclad/get_workflow' +import { listRecordsTool } from '@/tools/ironclad/list_records' +import { listWorkflowApprovalsTool } from '@/tools/ironclad/list_workflow_approvals' +import { listWorkflowCommentsTool } from '@/tools/ironclad/list_workflow_comments' +import { listWorkflowsTool } from '@/tools/ironclad/list_workflows' +import { updateRecordTool } from '@/tools/ironclad/update_record' +import { updateWorkflowMetadataTool } from '@/tools/ironclad/update_workflow_metadata' + +export const ironcladCreateWorkflowTool = createWorkflowTool +export const ironcladListWorkflowsTool = listWorkflowsTool +export const ironcladGetWorkflowTool = getWorkflowTool +export const ironcladUpdateWorkflowMetadataTool = updateWorkflowMetadataTool +export const ironcladCancelWorkflowTool = cancelWorkflowTool +export const ironcladListWorkflowApprovalsTool = listWorkflowApprovalsTool +export const ironcladAddCommentTool = addCommentTool +export const ironcladListWorkflowCommentsTool = listWorkflowCommentsTool +export const ironcladCreateRecordTool = createRecordTool +export const ironcladListRecordsTool = listRecordsTool +export const ironcladGetRecordTool = getRecordTool +export const ironcladUpdateRecordTool = updateRecordTool + +export * from './types' diff --git a/apps/sim/tools/ironclad/list_records.ts b/apps/sim/tools/ironclad/list_records.ts new file mode 100644 index 00000000000..bc6f0d89249 --- /dev/null +++ b/apps/sim/tools/ironclad/list_records.ts @@ -0,0 +1,90 @@ +import type { IroncladListRecordsParams, IroncladListRecordsResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listRecordsTool: ToolConfig = { + id: 'ironclad_list_records', + name: 'Ironclad List Records', + description: + 'List all records in Ironclad with pagination and optional filtering by last updated time.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number (starting from 0)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100)', + }, + lastUpdated: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter records updated on or after this ISO 8601 timestamp', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://na1.ironcladapp.com/public/api/v1/records') + if (params.page !== undefined) url.searchParams.set('page', String(params.page)) + if (params.pageSize !== undefined) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.lastUpdated) url.searchParams.set('lastUpdated', params.lastUpdated) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list records') + } + + const records = (data.list ?? []).map((r: Record) => ({ + id: (r.id as string) ?? '', + name: (r.name as string) ?? null, + type: (r.type as string) ?? null, + createdAt: (r.createdAt as string) ?? null, + updatedAt: (r.updatedAt as string) ?? null, + })) + + return { + success: true, + output: { + records, + page: data.page ?? 0, + pageSize: data.pageSize ?? 20, + count: data.count ?? 0, + }, + } + }, + + outputs: { + records: { type: 'json', description: 'List of records' }, + page: { type: 'number', description: 'Current page number' }, + pageSize: { type: 'number', description: 'Number of results per page' }, + count: { type: 'number', description: 'Total number of records' }, + }, +} diff --git a/apps/sim/tools/ironclad/list_workflow_approvals.ts b/apps/sim/tools/ironclad/list_workflow_approvals.ts new file mode 100644 index 00000000000..d968382ab3d --- /dev/null +++ b/apps/sim/tools/ironclad/list_workflow_approvals.ts @@ -0,0 +1,65 @@ +import type { + IroncladListWorkflowApprovalsParams, + IroncladListWorkflowApprovalsResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowApprovalsTool: ToolConfig< + IroncladListWorkflowApprovalsParams, + IroncladListWorkflowApprovalsResponse +> = { + id: 'ironclad_list_workflow_approvals', + name: 'Ironclad List Workflow Approvals', + description: + 'List all triggered approvals for a specific workflow. Conditional approvals that have not been triggered will not appear.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + ironcladWorkflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/approvals`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list workflow approvals') + } + + return { + success: true, + output: { + approvals: data.approvalGroups ?? data ?? [], + }, + } + }, + + outputs: { + approvals: { type: 'json', description: 'List of triggered approval groups for the workflow' }, + }, +} diff --git a/apps/sim/tools/ironclad/list_workflow_comments.ts b/apps/sim/tools/ironclad/list_workflow_comments.ts new file mode 100644 index 00000000000..dc4baad74f6 --- /dev/null +++ b/apps/sim/tools/ironclad/list_workflow_comments.ts @@ -0,0 +1,64 @@ +import type { + IroncladListWorkflowCommentsParams, + IroncladListWorkflowCommentsResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowCommentsTool: ToolConfig< + IroncladListWorkflowCommentsParams, + IroncladListWorkflowCommentsResponse +> = { + id: 'ironclad_list_workflow_comments', + name: 'Ironclad List Workflow Comments', + description: 'List all comments on a workflow.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + ironcladWorkflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/comments`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list workflow comments') + } + + return { + success: true, + output: { + comments: data.list ?? data.comments ?? data ?? [], + }, + } + }, + + outputs: { + comments: { type: 'json', description: 'List of comments on the workflow' }, + }, +} diff --git a/apps/sim/tools/ironclad/list_workflows.ts b/apps/sim/tools/ironclad/list_workflows.ts new file mode 100644 index 00000000000..353ea193cff --- /dev/null +++ b/apps/sim/tools/ironclad/list_workflows.ts @@ -0,0 +1,87 @@ +import type { + IroncladListWorkflowsParams, + IroncladListWorkflowsResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowsTool: ToolConfig< + IroncladListWorkflowsParams, + IroncladListWorkflowsResponse +> = { + id: 'ironclad_list_workflows', + name: 'Ironclad List Workflows', + description: 'List all workflows in Ironclad with pagination support.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number (starting from 0)', + }, + perPage: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100)', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://na1.ironcladapp.com/public/api/v1/workflows') + if (params.page !== undefined) url.searchParams.set('page', String(params.page)) + if (params.perPage !== undefined) url.searchParams.set('perPage', String(params.perPage)) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list workflows') + } + + const workflows = (data.list ?? []).map((w: Record) => ({ + id: (w.id as string) ?? '', + status: (w.status as string) ?? null, + template: (w.template as string) ?? null, + creator: (w.creator as string) ?? null, + })) + + return { + success: true, + output: { + workflows, + page: data.page ?? 0, + pageSize: data.pageSize ?? data.perPage ?? 20, + count: data.count ?? 0, + }, + } + }, + + outputs: { + workflows: { type: 'json', description: 'List of workflows' }, + page: { type: 'number', description: 'Current page number' }, + pageSize: { type: 'number', description: 'Number of results per page' }, + count: { type: 'number', description: 'Total number of workflows' }, + }, +} diff --git a/apps/sim/tools/ironclad/types.ts b/apps/sim/tools/ironclad/types.ts new file mode 100644 index 00000000000..6ba57460863 --- /dev/null +++ b/apps/sim/tools/ironclad/types.ts @@ -0,0 +1,199 @@ +import type { ToolResponse } from '@/tools/types' + +/** Base parameters shared by all Ironclad tools */ +export interface IroncladBaseParams { + accessToken: string +} + +/** Create Workflow params */ +export interface IroncladCreateWorkflowParams extends IroncladBaseParams { + template: string + attributes?: string +} + +/** Create Workflow response */ +export interface IroncladCreateWorkflowResponse extends ToolResponse { + output: { + id: string + status: string | null + template: string | null + creator: string | null + } +} + +/** List Workflows params */ +export interface IroncladListWorkflowsParams extends IroncladBaseParams { + page?: number + perPage?: number +} + +/** List Workflows response */ +export interface IroncladListWorkflowsResponse extends ToolResponse { + output: { + workflows: IroncladWorkflowSummary[] + page: number + pageSize: number + count: number + } +} + +/** Get Workflow params */ +export interface IroncladGetWorkflowParams extends IroncladBaseParams { + ironcladWorkflowId: string +} + +/** Get Workflow response */ +export interface IroncladGetWorkflowResponse extends ToolResponse { + output: { + id: string + status: string | null + template: string | null + creator: string | null + step: string | null + attributes: Record | null + } +} + +/** Update Workflow Metadata params */ +export interface IroncladUpdateWorkflowMetadataParams extends IroncladBaseParams { + ironcladWorkflowId: string + actions: string +} + +/** Update Workflow Metadata response */ +export interface IroncladUpdateWorkflowMetadataResponse extends ToolResponse { + output: { + success: boolean + } +} + +/** Create Record params */ +export interface IroncladCreateRecordParams extends IroncladBaseParams { + recordType: string + name: string + properties?: string + links?: string +} + +/** Create Record response */ +export interface IroncladCreateRecordResponse extends ToolResponse { + output: { + id: string + name: string + type: string | null + } +} + +/** List Records params */ +export interface IroncladListRecordsParams extends IroncladBaseParams { + page?: number + pageSize?: number + lastUpdated?: string +} + +/** List Records response */ +export interface IroncladListRecordsResponse extends ToolResponse { + output: { + records: IroncladRecordSummary[] + page: number + pageSize: number + count: number + } +} + +/** Get Record params */ +export interface IroncladGetRecordParams extends IroncladBaseParams { + recordId: string +} + +/** Get Record response */ +export interface IroncladGetRecordResponse extends ToolResponse { + output: { + id: string + name: string | null + type: string | null + properties: Record | null + createdAt: string | null + updatedAt: string | null + } +} + +/** Update Record params */ +export interface IroncladUpdateRecordParams extends IroncladBaseParams { + recordId: string + properties: string +} + +/** Update Record response */ +export interface IroncladUpdateRecordResponse extends ToolResponse { + output: { + id: string + name: string | null + type: string | null + } +} + +/** Cancel Workflow params */ +export interface IroncladCancelWorkflowParams extends IroncladBaseParams { + ironcladWorkflowId: string +} + +/** Cancel Workflow response */ +export interface IroncladCancelWorkflowResponse extends ToolResponse { + output: { + success: boolean + } +} + +/** List Workflow Approvals params */ +export interface IroncladListWorkflowApprovalsParams extends IroncladBaseParams { + ironcladWorkflowId: string +} + +/** List Workflow Approvals response */ +export interface IroncladListWorkflowApprovalsResponse extends ToolResponse { + output: { + approvals: unknown[] + } +} + +/** Add Comment params */ +export interface IroncladAddCommentParams extends IroncladBaseParams { + ironcladWorkflowId: string + comment: string +} + +/** Add Comment response */ +export interface IroncladAddCommentResponse extends ToolResponse { + output: { + success: boolean + } +} + +/** List Workflow Comments params */ +export interface IroncladListWorkflowCommentsParams extends IroncladBaseParams { + ironcladWorkflowId: string +} + +/** List Workflow Comments response */ +export interface IroncladListWorkflowCommentsResponse extends ToolResponse { + output: { + comments: unknown[] + } +} + +/** Shared summary types */ +export interface IroncladWorkflowSummary { + id: string + status: string | null + template: string | null + creator: string | null +} + +export interface IroncladRecordSummary { + id: string + name: string | null + type: string | null + createdAt: string | null + updatedAt: string | null +} diff --git a/apps/sim/tools/ironclad/update_record.ts b/apps/sim/tools/ironclad/update_record.ts new file mode 100644 index 00000000000..8a5e185ae01 --- /dev/null +++ b/apps/sim/tools/ironclad/update_record.ts @@ -0,0 +1,81 @@ +import type { + IroncladUpdateRecordParams, + IroncladUpdateRecordResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const updateRecordTool: ToolConfig< + IroncladUpdateRecordParams, + IroncladUpdateRecordResponse +> = { + id: 'ironclad_update_record', + name: 'Ironclad Update Record', + description: 'Update metadata fields on an existing record.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + recordId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the record to update', + }, + properties: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'JSON object of fields to update (e.g., {"fieldName": "newValue"})', + }, + }, + + request: { + url: (params) => `https://na1.ironcladapp.com/public/api/v1/records/${params.recordId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + try { + return JSON.parse(params.properties) + } catch { + throw new Error('Invalid JSON in properties field') + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to update record') + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? null, + type: data.type ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The record ID' }, + name: { type: 'string', description: 'The record name' }, + type: { type: 'string', description: 'The record type' }, + }, +} diff --git a/apps/sim/tools/ironclad/update_workflow_metadata.ts b/apps/sim/tools/ironclad/update_workflow_metadata.ts new file mode 100644 index 00000000000..a19a382f480 --- /dev/null +++ b/apps/sim/tools/ironclad/update_workflow_metadata.ts @@ -0,0 +1,83 @@ +import type { + IroncladUpdateWorkflowMetadataParams, + IroncladUpdateWorkflowMetadataResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const updateWorkflowMetadataTool: ToolConfig< + IroncladUpdateWorkflowMetadataParams, + IroncladUpdateWorkflowMetadataResponse +> = { + id: 'ironclad_update_workflow_metadata', + name: 'Ironclad Update Workflow Metadata', + description: + 'Update attributes on a workflow. The workflow must be in the Review step. Supports set and remove actions.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + ironcladWorkflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + actions: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'JSON array of actions. Each action has "action" (set/remove), "field", and optionally "value".', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/attributes`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + try { + return { actions: JSON.parse(params.actions) } + } catch { + throw new Error('Invalid JSON in actions field') + } + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json().catch(() => ({})) + throw new Error( + (data as Record).message || + (data as Record).error || + 'Failed to update workflow metadata' + ) + } + + return { + success: true, + output: { + success: true, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the update was successful' }, + }, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 99efb6dcbbc..3469d891c64 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1172,6 +1172,20 @@ import { intercomUpdateContactV2Tool, intercomUpdateTicketV2Tool, } from '@/tools/intercom' +import { + ironcladAddCommentTool, + ironcladCancelWorkflowTool, + ironcladCreateRecordTool, + ironcladCreateWorkflowTool, + ironcladGetRecordTool, + ironcladGetWorkflowTool, + ironcladListRecordsTool, + ironcladListWorkflowApprovalsTool, + ironcladListWorkflowCommentsTool, + ironcladListWorkflowsTool, + ironcladUpdateRecordTool, + ironcladUpdateWorkflowMetadataTool, +} from '@/tools/ironclad' import { jinaReadUrlTool, jinaSearchTool } from '@/tools/jina' import { jiraAddAttachmentTool, @@ -4696,6 +4710,18 @@ export const tools: Record = { intercom_create_event_v2: intercomCreateEventV2Tool, intercom_attach_contact_to_company_v2: intercomAttachContactToCompanyV2Tool, intercom_detach_contact_from_company_v2: intercomDetachContactFromCompanyV2Tool, + ironclad_add_comment: ironcladAddCommentTool, + ironclad_cancel_workflow: ironcladCancelWorkflowTool, + ironclad_create_record: ironcladCreateRecordTool, + ironclad_create_workflow: ironcladCreateWorkflowTool, + ironclad_get_record: ironcladGetRecordTool, + ironclad_get_workflow: ironcladGetWorkflowTool, + ironclad_list_records: ironcladListRecordsTool, + ironclad_list_workflow_approvals: ironcladListWorkflowApprovalsTool, + ironclad_list_workflow_comments: ironcladListWorkflowCommentsTool, + ironclad_list_workflows: ironcladListWorkflowsTool, + ironclad_update_record: ironcladUpdateRecordTool, + ironclad_update_workflow_metadata: ironcladUpdateWorkflowMetadataTool, sentry_issues_list: listIssuesTool, sentry_issues_get: getIssueTool, sentry_issues_update: updateIssueTool,