diff --git a/apps/sim/blocks/blocks/hex.ts b/apps/sim/blocks/blocks/hex.ts index 2bb36f75ef6..76cf338782c 100644 --- a/apps/sim/blocks/blocks/hex.ts +++ b/apps/sim/blocks/blocks/hex.ts @@ -8,7 +8,7 @@ export const HexBlock: BlockConfig = { name: 'Hex', description: 'Run and manage Hex projects', longDescription: - 'Integrate Hex into your workflow. Run projects, check run status, manage collections and groups, list users, and view data connections. Requires a Hex API token.', + 'Integrate Hex into your workflow. Run projects, check run status, manage collections and groups (including membership and deactivating users), list users, and view data connections. Requires a Hex API token.', docsLink: 'https://docs.sim.ai/integrations/hex', category: 'tools', integrationType: IntegrationType.Analytics, @@ -36,8 +36,13 @@ export const HexBlock: BlockConfig = { { label: 'List Collections', id: 'list_collections' }, { label: 'Get Collection', id: 'get_collection' }, { label: 'Create Collection', id: 'create_collection' }, + { label: 'Update Collection', id: 'update_collection' }, { label: 'List Data Connections', id: 'list_data_connections' }, { label: 'Get Data Connection', id: 'get_data_connection' }, + { label: 'Create Group', id: 'create_group' }, + { label: 'Update Group', id: 'update_group' }, + { label: 'Delete Group', id: 'delete_group' }, + { label: 'Deactivate User', id: 'deactivate_user' }, ], value: () => 'run_project', }, @@ -107,6 +112,39 @@ Example: generationType: 'json-object', }, }, + { + id: 'viewId', + title: 'Saved View ID', + type: 'short-input', + placeholder: 'Enter a SavedView UUID (optional)', + condition: { field: 'operation', value: 'run_project' }, + mode: 'advanced', + }, + { + id: 'notifications', + title: 'Notifications', + type: 'code', + placeholder: '[{"type": "FAILURE", "slackChannelIds": ["C0123456789"]}]', + condition: { field: 'operation', value: 'run_project' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating Hex run notification configs. +Generate ONLY the raw JSON array based on the user's request. +The output MUST be a single, valid JSON array, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array. +Each item's "type" must be one of ALL, SUCCESS, or FAILURE. Optional fields: includeSuccessScreenshot (boolean), slackChannelIds, userIds, groupIds (arrays of strings). + +Example: +[{"type": "FAILURE", "slackChannelIds": ["C0123456789"], "includeSuccessScreenshot": false}]`, + placeholder: 'Describe who should be notified and when...', + generationType: 'json-object', + }, + }, { id: 'projectStatus', title: 'Status', @@ -130,28 +168,119 @@ Example: value: () => '', condition: { field: 'operation', value: 'get_project_runs' }, }, + { + id: 'runTriggerFilter', + title: 'Trigger Filter', + type: 'dropdown', + options: [ + { label: 'All', id: 'ALL' }, + { label: 'API', id: 'API' }, + { label: 'Scheduled', id: 'SCHEDULED' }, + { label: 'App Refresh', id: 'APP_REFRESH' }, + ], + value: () => 'ALL', + condition: { field: 'operation', value: 'get_project_runs' }, + mode: 'advanced', + }, { id: 'groupIdInput', title: 'Group ID', type: 'short-input', placeholder: 'Enter group UUID', - condition: { field: 'operation', value: 'get_group' }, - required: { field: 'operation', value: 'get_group' }, + condition: { field: 'operation', value: ['get_group', 'update_group', 'delete_group'] }, + required: { field: 'operation', value: ['get_group', 'update_group', 'delete_group'] }, + }, + { + id: 'groupName', + title: 'Group Name', + type: 'short-input', + placeholder: 'Enter group name', + condition: { field: 'operation', value: ['create_group', 'update_group'] }, + required: { field: 'operation', value: 'create_group' }, + }, + { + id: 'groupMemberUserIds', + title: 'Initial Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'create_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array. + +Example: +["a1b2c3d4-0000-0000-0000-000000000000", "e5f6a7b8-0000-0000-0000-000000000000"]`, + placeholder: 'Describe which users to add...', + generationType: 'json-object', + }, + }, + { + id: 'groupAddUserIds', + title: 'Add Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'update_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings to add based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array.`, + placeholder: 'Describe which users to add...', + generationType: 'json-object', + }, + }, + { + id: 'groupRemoveUserIds', + title: 'Remove Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'update_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings to remove based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array.`, + placeholder: 'Describe which users to remove...', + generationType: 'json-object', + }, }, { id: 'collectionId', title: 'Collection ID', type: 'short-input', placeholder: 'Enter collection UUID', - condition: { field: 'operation', value: 'get_collection' }, - required: { field: 'operation', value: 'get_collection' }, + condition: { + field: 'operation', + value: ['get_collection', 'update_collection', 'list_projects'], + }, + required: { field: 'operation', value: ['get_collection', 'update_collection'] }, }, { id: 'collectionName', title: 'Collection Name', type: 'short-input', placeholder: 'Enter collection name', - condition: { field: 'operation', value: 'create_collection' }, + condition: { field: 'operation', value: ['create_collection', 'update_collection'] }, required: { field: 'operation', value: 'create_collection' }, }, { @@ -159,7 +288,7 @@ Example: title: 'Description', type: 'long-input', placeholder: 'Optional description for the collection', - condition: { field: 'operation', value: 'create_collection' }, + condition: { field: 'operation', value: ['create_collection', 'update_collection'] }, }, { id: 'dataConnectionId', @@ -169,6 +298,14 @@ Example: condition: { field: 'operation', value: 'get_data_connection' }, required: { field: 'operation', value: 'get_data_connection' }, }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'Enter user UUID', + condition: { field: 'operation', value: 'deactivate_user' }, + required: { field: 'operation', value: 'deactivate_user' }, + }, { id: 'apiKey', title: 'API Key', @@ -240,6 +377,20 @@ Example: condition: { field: 'operation', value: 'list_projects' }, mode: 'advanced', }, + { + id: 'includeComponents', + title: 'Include Components', + type: 'switch', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'includeTrashed', + title: 'Include Trashed', + type: 'switch', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, { id: 'statusFilter', title: 'Status Filter', @@ -253,6 +404,57 @@ Example: condition: { field: 'operation', value: 'list_projects' }, mode: 'advanced', }, + { + id: 'creatorEmail', + title: 'Creator Email', + type: 'short-input', + placeholder: 'Filter by creator email (optional)', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'ownerEmail', + title: 'Owner Email', + type: 'short-input', + placeholder: 'Filter by owner email (optional)', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'categories', + title: 'Categories', + type: 'code', + placeholder: '["Marketing", "Finance"]', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'sortBy', + title: 'Sort By', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Created At', id: 'CREATED_AT' }, + { label: 'Last Edited At', id: 'LAST_EDITED_AT' }, + { label: 'Last Published At', id: 'LAST_PUBLISHED_AT' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'sortDirection', + title: 'Sort Direction', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Ascending', id: 'ASC' }, + { label: 'Descending', id: 'DESC' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, { id: 'groupId', title: 'Filter by Group', @@ -261,12 +463,57 @@ Example: condition: { field: 'operation', value: 'list_users' }, mode: 'advanced', }, + { + id: 'userIds', + title: 'Filter by User IDs', + type: 'short-input', + placeholder: 'Comma-separated user UUIDs (optional)', + condition: { field: 'operation', value: 'list_users' }, + mode: 'advanced', + }, + { + id: 'after', + title: 'After Cursor', + type: 'short-input', + placeholder: 'Cursor for the next page', + condition: { + field: 'operation', + value: [ + 'list_projects', + 'list_groups', + 'list_collections', + 'list_data_connections', + 'list_users', + ], + }, + mode: 'advanced', + }, + { + id: 'before', + title: 'Before Cursor', + type: 'short-input', + placeholder: 'Cursor for the previous page', + condition: { + field: 'operation', + value: [ + 'list_projects', + 'list_groups', + 'list_collections', + 'list_data_connections', + 'list_users', + ], + }, + mode: 'advanced', + }, ], tools: { access: [ 'hex_cancel_run', 'hex_create_collection', + 'hex_create_group', + 'hex_deactivate_user', + 'hex_delete_group', 'hex_get_collection', 'hex_get_data_connection', 'hex_get_group', @@ -280,6 +527,8 @@ Example: 'hex_list_projects', 'hex_list_users', 'hex_run_project', + 'hex_update_collection', + 'hex_update_group', 'hex_update_project', ], config: { @@ -313,10 +562,20 @@ Example: return 'hex_get_collection' case 'create_collection': return 'hex_create_collection' + case 'update_collection': + return 'hex_update_collection' case 'list_data_connections': return 'hex_list_data_connections' case 'get_data_connection': return 'hex_get_data_connection' + case 'create_group': + return 'hex_create_group' + case 'update_group': + return 'hex_update_group' + case 'delete_group': + return 'hex_delete_group' + case 'deactivate_user': + return 'hex_deactivate_user' default: return 'hex_run_project' } @@ -330,11 +589,26 @@ Example: if (op === 'update_project' && params.projectStatus) result.status = params.projectStatus if (op === 'get_project_runs' && params.runStatusFilter) result.statusFilter = params.runStatusFilter - if (op === 'get_group' && params.groupIdInput) result.groupId = params.groupIdInput + if ( + (op === 'get_group' || op === 'update_group' || op === 'delete_group') && + params.groupIdInput + ) + result.groupId = params.groupIdInput if (op === 'list_users' && params.groupId) result.groupId = params.groupId - if (op === 'create_collection' && params.collectionName) result.name = params.collectionName + if ((op === 'create_collection' || op === 'update_collection') && params.collectionName) + result.name = params.collectionName if (op === 'create_collection' && params.collectionDescription) result.description = params.collectionDescription + if (op === 'update_collection' && params.collectionDescription != null) + result.description = params.collectionDescription + if ((op === 'create_group' || op === 'update_group') && params.groupName) + result.name = params.groupName + if (op === 'create_group' && params.groupMemberUserIds) + result.memberUserIds = params.groupMemberUserIds + if (op === 'update_group' && params.groupAddUserIds) + result.addUserIds = params.groupAddUserIds + if (op === 'update_group' && params.groupRemoveUserIds) + result.removeUserIds = params.groupRemoveUserIds return result }, @@ -360,21 +634,42 @@ Example: type: 'boolean', description: 'Use cached SQL results instead of re-running queries', }, + viewId: { type: 'string', description: 'SavedView UUID to use for the project run' }, + notifications: { + type: 'json', + description: 'Notification details to deliver once the run completes', + }, projectStatus: { type: 'string', description: 'New project status name (custom workspace status label)', }, limit: { type: 'number', description: 'Max number of results to return' }, offset: { type: 'number', description: 'Offset for paginated results' }, + after: { type: 'string', description: 'Cursor to fetch results after' }, + before: { type: 'string', description: 'Cursor to fetch results before' }, includeArchived: { type: 'boolean', description: 'Include archived projects' }, + includeComponents: { type: 'boolean', description: 'Include components in results' }, + includeTrashed: { type: 'boolean', description: 'Include trashed projects in results' }, statusFilter: { type: 'string', description: 'Filter projects by status' }, + creatorEmail: { type: 'string', description: 'Filter projects by creator email' }, + ownerEmail: { type: 'string', description: 'Filter projects by owner email' }, + categories: { type: 'json', description: 'Filter projects by category names' }, + sortBy: { type: 'string', description: 'Sort field for list results' }, + sortDirection: { type: 'string', description: 'Sort direction for list results' }, runStatusFilter: { type: 'string', description: 'Filter runs by status' }, + runTriggerFilter: { type: 'string', description: 'Filter runs by trigger source' }, groupId: { type: 'string', description: 'Filter users by group UUID' }, - groupIdInput: { type: 'string', description: 'Group UUID for get group' }, + userIds: { type: 'string', description: 'Comma-separated user UUIDs to filter by' }, + groupIdInput: { type: 'string', description: 'Group UUID for get/update/delete group' }, + groupName: { type: 'string', description: 'Group name' }, + groupMemberUserIds: { type: 'json', description: 'Initial member user UUIDs for new group' }, + groupAddUserIds: { type: 'json', description: 'User UUIDs to add to the group' }, + groupRemoveUserIds: { type: 'json', description: 'User UUIDs to remove from the group' }, collectionId: { type: 'string', description: 'Collection UUID' }, collectionName: { type: 'string', description: 'Collection name' }, collectionDescription: { type: 'string', description: 'Collection description' }, dataConnectionId: { type: 'string', description: 'Data connection UUID' }, + userId: { type: 'string', description: 'User UUID' }, }, outputs: { @@ -415,7 +710,10 @@ Example: description: 'List of runs with runId, status, runUrl, startTime, endTime, elapsedTime, projectVersion', }, - users: { type: 'json', description: 'List of users with id, name, email, role' }, + users: { + type: 'json', + description: 'List of users with id, name, email, role, lastLoginDate', + }, groups: { type: 'json', description: 'List of groups with id, name, createdAt' }, collections: { type: 'json', @@ -437,8 +735,15 @@ Example: creator: { type: 'json', description: 'Creator details ({ email, id })' }, owner: { type: 'json', description: 'Owner details ({ email })' }, total: { type: 'number', description: 'Total results returned' }, - // Cancel output + // Cancel / delete / deactivate output success: { type: 'boolean', description: 'Whether the operation succeeded' }, + groupId: { type: 'string', description: 'Group UUID' }, + userId: { type: 'string', description: 'User UUID' }, + // Pagination + nextPage: { type: 'string', description: 'Cursor for the next page of runs' }, + previousPage: { type: 'string', description: 'Cursor for the previous page of runs' }, + after: { type: 'string', description: 'Cursor for the next page of results' }, + before: { type: 'string', description: 'Cursor for the previous page of results' }, // Data connection flags connectViaSsh: { type: 'boolean', description: 'SSH tunneling enabled' }, includeMagic: { type: 'boolean', description: 'Magic AI features enabled' }, @@ -546,5 +851,12 @@ export const HexBlockMeta = { content: '# Inventory Projects\n\nMap what projects and data sources exist in the workspace.\n\n## Steps\n1. List projects and capture IDs, names, and owners.\n2. List collections and get details to see how projects are grouped.\n3. List data connections to map which sources power the projects.\n4. Cross-reference projects to their collections and data connections.\n\n## Output\nReturn an inventory of projects grouped by collection, each annotated with its data connections. Useful for governance and cleanup.', }, + { + name: 'onboard-offboard-teammate', + description: + 'Add a new teammate to the right Hex groups on hire, or deactivate and remove them on departure.', + content: + '# Onboard/Offboard Teammate\n\nManage workspace access as people join or leave the team.\n\n## Steps\n1. List users to resolve the target user by name or email, and list groups to resolve the relevant group by name.\n2. For onboarding: add the user to the appropriate group(s) via group update.\n3. For offboarding: remove the user from their groups via group update, then deactivate the user account.\n4. Confirm the change by getting the group or listing users filtered by group.\n\n## Output\nReturn the user and group IDs affected and the action taken (added, removed, deactivated). Flag if the user or group could not be resolved.', + }, ], } as const satisfies BlockMeta diff --git a/apps/sim/tools/hex/cancel_run.ts b/apps/sim/tools/hex/cancel_run.ts index 17c65944c3a..f7a9646b495 100644 --- a/apps/sim/tools/hex/cancel_run.ts +++ b/apps/sim/tools/hex/cancel_run.ts @@ -30,7 +30,7 @@ export const cancelRunTool: ToolConfig request: { url: (params) => - `https://app.hex.tech/api/v1/projects/${params.projectId}/runs/${params.runId}`, + `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs/${params.runId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/create_group.ts b/apps/sim/tools/hex/create_group.ts new file mode 100644 index 00000000000..cac1e503b3e --- /dev/null +++ b/apps/sim/tools/hex/create_group.ts @@ -0,0 +1,78 @@ +import type { HexCreateGroupParams, HexCreateGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const createGroupTool: ToolConfig = { + id: 'hex_create_group', + name: 'Hex Create Group', + description: 'Create a new group in the Hex workspace, optionally with initial members.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name for the new group', + }, + memberUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'JSON array of user UUIDs to add as initial group members (e.g., ["uuid1", "uuid2"])', + }, + }, + + request: { + url: 'https://app.hex.tech/api/v1/groups', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { name: params.name } + if (params.memberUserIds) { + let userIds: unknown + try { + userIds = + typeof params.memberUserIds === 'string' + ? JSON.parse(params.memberUserIds) + : params.memberUserIds + } catch { + throw new Error('memberUserIds must be a valid JSON array of user UUID strings') + } + if (!Array.isArray(userIds) || !userIds.every((id) => typeof id === 'string')) { + throw new Error('memberUserIds must be a valid JSON array of user UUID strings') + } + body.members = { users: userIds.map((id: string) => ({ id: id.trim() })) } + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + createdAt: data.createdAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Newly created group UUID' }, + name: { type: 'string', description: 'Group name' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + }, +} diff --git a/apps/sim/tools/hex/deactivate_user.ts b/apps/sim/tools/hex/deactivate_user.ts new file mode 100644 index 00000000000..1f75120e751 --- /dev/null +++ b/apps/sim/tools/hex/deactivate_user.ts @@ -0,0 +1,60 @@ +import type { HexDeactivateUserParams, HexDeactivateUserResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const deactivateUserTool: ToolConfig = { + id: 'hex_deactivate_user', + name: 'Hex Deactivate User', + description: 'Deactivate a user in the Hex workspace.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the user to deactivate', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/users/${params.userId.trim()}/deactivate`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response, params) => { + if (response.status === 204 || response.ok) { + return { + success: true, + output: { + success: true, + userId: params?.userId ?? '', + }, + } + } + + const data = await response.json().catch(() => ({})) + return { + success: false, + output: { + success: false, + userId: params?.userId ?? '', + }, + error: (data as Record).message ?? 'Failed to deactivate user', + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the user was successfully deactivated' }, + userId: { type: 'string', description: 'User UUID that was deactivated' }, + }, +} diff --git a/apps/sim/tools/hex/delete_group.ts b/apps/sim/tools/hex/delete_group.ts new file mode 100644 index 00000000000..1135a6c87e2 --- /dev/null +++ b/apps/sim/tools/hex/delete_group.ts @@ -0,0 +1,60 @@ +import type { HexDeleteGroupParams, HexDeleteGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteGroupTool: ToolConfig = { + id: 'hex_delete_group', + name: 'Hex Delete Group', + description: 'Delete a group from the Hex workspace.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + groupId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the group to delete', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response, params) => { + if (response.status === 204 || response.ok) { + return { + success: true, + output: { + success: true, + groupId: params?.groupId ?? '', + }, + } + } + + const data = await response.json().catch(() => ({})) + return { + success: false, + output: { + success: false, + groupId: params?.groupId ?? '', + }, + error: (data as Record).message ?? 'Failed to delete group', + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the group was successfully deleted' }, + groupId: { type: 'string', description: 'Group UUID that was deleted' }, + }, +} diff --git a/apps/sim/tools/hex/get_collection.ts b/apps/sim/tools/hex/get_collection.ts index 8222d88a92c..8b5c0fe888c 100644 --- a/apps/sim/tools/hex/get_collection.ts +++ b/apps/sim/tools/hex/get_collection.ts @@ -23,7 +23,7 @@ export const getCollectionTool: ToolConfig `https://app.hex.tech/api/v1/collections/${params.collectionId}`, + url: (params) => `https://app.hex.tech/api/v1/collections/${params.collectionId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_data_connection.ts b/apps/sim/tools/hex/get_data_connection.ts index 3b9e54b94f3..68bd0838389 100644 --- a/apps/sim/tools/hex/get_data_connection.ts +++ b/apps/sim/tools/hex/get_data_connection.ts @@ -27,7 +27,8 @@ export const getDataConnectionTool: ToolConfig< }, request: { - url: (params) => `https://app.hex.tech/api/v1/data-connections/${params.dataConnectionId}`, + url: (params) => + `https://app.hex.tech/api/v1/data-connections/${params.dataConnectionId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_group.ts b/apps/sim/tools/hex/get_group.ts index c649e657a86..683bde7e519 100644 --- a/apps/sim/tools/hex/get_group.ts +++ b/apps/sim/tools/hex/get_group.ts @@ -23,7 +23,7 @@ export const getGroupTool: ToolConfig = }, request: { - url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId}`, + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_project.ts b/apps/sim/tools/hex/get_project.ts index fda718f2f6c..fd5ecb29f34 100644 --- a/apps/sim/tools/hex/get_project.ts +++ b/apps/sim/tools/hex/get_project.ts @@ -24,7 +24,7 @@ export const getProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_project_runs.ts b/apps/sim/tools/hex/get_project_runs.ts index 9d2897d900e..0495603de27 100644 --- a/apps/sim/tools/hex/get_project_runs.ts +++ b/apps/sim/tools/hex/get_project_runs.ts @@ -40,6 +40,12 @@ export const getProjectRunsTool: ToolConfig ({ @@ -78,6 +85,8 @@ export const getProjectRunsTool: ToolConfig ({ diff --git a/apps/sim/tools/hex/get_run_status.ts b/apps/sim/tools/hex/get_run_status.ts index 90dd26cdb01..0c0de4a870b 100644 --- a/apps/sim/tools/hex/get_run_status.ts +++ b/apps/sim/tools/hex/get_run_status.ts @@ -31,7 +31,7 @@ export const getRunStatusTool: ToolConfig - `https://app.hex.tech/api/v1/projects/${params.projectId}/runs/${params.runId}`, + `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs/${params.runId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/index.ts b/apps/sim/tools/hex/index.ts index 9a561587d7d..e96b0bb53fb 100644 --- a/apps/sim/tools/hex/index.ts +++ b/apps/sim/tools/hex/index.ts @@ -1,5 +1,8 @@ import { cancelRunTool } from '@/tools/hex/cancel_run' import { createCollectionTool } from '@/tools/hex/create_collection' +import { createGroupTool } from '@/tools/hex/create_group' +import { deactivateUserTool } from '@/tools/hex/deactivate_user' +import { deleteGroupTool } from '@/tools/hex/delete_group' import { getCollectionTool } from '@/tools/hex/get_collection' import { getDataConnectionTool } from '@/tools/hex/get_data_connection' import { getGroupTool } from '@/tools/hex/get_group' @@ -13,10 +16,15 @@ import { listGroupsTool } from '@/tools/hex/list_groups' import { listProjectsTool } from '@/tools/hex/list_projects' import { listUsersTool } from '@/tools/hex/list_users' import { runProjectTool } from '@/tools/hex/run_project' +import { updateCollectionTool } from '@/tools/hex/update_collection' +import { updateGroupTool } from '@/tools/hex/update_group' import { updateProjectTool } from '@/tools/hex/update_project' export const hexCancelRunTool = cancelRunTool export const hexCreateCollectionTool = createCollectionTool +export const hexCreateGroupTool = createGroupTool +export const hexDeactivateUserTool = deactivateUserTool +export const hexDeleteGroupTool = deleteGroupTool export const hexGetCollectionTool = getCollectionTool export const hexGetDataConnectionTool = getDataConnectionTool export const hexGetGroupTool = getGroupTool @@ -30,4 +38,6 @@ export const hexListGroupsTool = listGroupsTool export const hexListProjectsTool = listProjectsTool export const hexListUsersTool = listUsersTool export const hexRunProjectTool = runProjectTool +export const hexUpdateCollectionTool = updateCollectionTool +export const hexUpdateGroupTool = updateGroupTool export const hexUpdateProjectTool = updateProjectTool diff --git a/apps/sim/tools/hex/list_collections.ts b/apps/sim/tools/hex/list_collections.ts index 9902db0d158..beaf4054f57 100644 --- a/apps/sim/tools/hex/list_collections.ts +++ b/apps/sim/tools/hex/list_collections.ts @@ -27,6 +27,18 @@ export const listCollectionsTool: ToolConfig typeof c === 'string')) { + throw new Error('categories must be a valid JSON array of category name strings') + } + for (const category of categories) { + searchParams.append('categories[]', category) + } + } + if (params.sortBy) searchParams.set('sortBy', params.sortBy) + if (params.sortDirection) searchParams.set('sortDirection', params.sortDirection) + if (params.after) searchParams.set('after', params.after) + if (params.before) searchParams.set('before', params.before) const qs = searchParams.toString() return `https://app.hex.tech/api/v1/projects${qs ? `?${qs}` : ''}` }, @@ -80,6 +166,8 @@ export const listProjectsTool: ToolConfig visibility: 'user-or-llm', description: 'Filter users by group UUID', }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of user UUIDs to filter by', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor to fetch the page of results after this value', + }, + before: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor to fetch the page of results before this value', + }, }, request: { @@ -47,6 +65,9 @@ export const listUsersTool: ToolConfig if (params.sortBy) searchParams.set('sortBy', params.sortBy) if (params.sortDirection) searchParams.set('sortDirection', params.sortDirection) if (params.groupId) searchParams.set('groupId', params.groupId) + if (params.userIds) searchParams.set('userIds', params.userIds) + if (params.after) searchParams.set('after', params.after) + if (params.before) searchParams.set('before', params.before) const qs = searchParams.toString() return `https://app.hex.tech/api/v1/users${qs ? `?${qs}` : ''}` }, @@ -69,8 +90,11 @@ export const listUsersTool: ToolConfig name: (u.name as string) ?? null, email: (u.email as string) ?? null, role: (u.role as string) ?? null, + lastLoginDate: (u.lastLoginDate as string) ?? null, })), total: users.length, + after: data.pagination?.after ?? null, + before: data.pagination?.before ?? null, }, } }, @@ -90,9 +114,20 @@ export const listUsersTool: ToolConfig description: 'User role (ADMIN, MANAGER, EDITOR, EXPLORER, MEMBER, GUEST, EMBEDDED_USER, ANONYMOUS)', }, + lastLoginDate: { + type: 'string', + description: 'Last login timestamp', + optional: true, + }, }, }, }, total: { type: 'number', description: 'Total number of users returned' }, + after: { type: 'string', description: 'Cursor for the next page of results', optional: true }, + before: { + type: 'string', + description: 'Cursor for the previous page of results', + optional: true, + }, }, } diff --git a/apps/sim/tools/hex/run_project.ts b/apps/sim/tools/hex/run_project.ts index 3d11ac03466..6d1a9ff6022 100644 --- a/apps/sim/tools/hex/run_project.ts +++ b/apps/sim/tools/hex/run_project.ts @@ -52,10 +52,23 @@ export const runProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}/runs`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -65,10 +78,14 @@ export const runProjectTool: ToolConfig = {} if (params.inputParams) { - body.inputParams = - typeof params.inputParams === 'string' - ? JSON.parse(params.inputParams) - : params.inputParams + try { + body.inputParams = + typeof params.inputParams === 'string' + ? JSON.parse(params.inputParams) + : params.inputParams + } catch { + throw new Error('inputParams must be valid JSON') + } } if (params.dryRun !== undefined) body.dryRun = params.dryRun if (params.updateCache !== undefined) body.updateCache = params.updateCache @@ -76,6 +93,22 @@ export const runProjectTool: ToolConfig total: number + after: string | null + before: string | null } } @@ -159,6 +171,8 @@ export interface HexRunProjectParams { updateCache?: boolean updatePublishedResults?: boolean useCachedSqlResults?: boolean + viewId?: string + notifications?: string } export interface HexRunProjectResponse extends ToolResponse { @@ -212,6 +226,7 @@ export interface HexGetProjectRunsParams { limit?: number offset?: number statusFilter?: string + runTriggerFilter?: string } export interface HexGetProjectRunsResponse extends ToolResponse { @@ -229,6 +244,8 @@ export interface HexGetProjectRunsResponse extends ToolResponse { }> total: number traceId: string | null + nextPage: string | null + previousPage: string | null } } @@ -262,6 +279,9 @@ export interface HexListUsersParams { sortBy?: string sortDirection?: string groupId?: string + userIds?: string + after?: string + before?: string } export interface HexListUsersResponse extends ToolResponse { @@ -271,8 +291,11 @@ export interface HexListUsersResponse extends ToolResponse { name: string email: string role: string + lastLoginDate: string | null }> total: number + after: string | null + before: string | null } } @@ -280,6 +303,8 @@ export interface HexListCollectionsParams { apiKey: string limit?: number sortBy?: string + after?: string + before?: string } export interface HexListCollectionsResponse extends ToolResponse { @@ -291,6 +316,8 @@ export interface HexListCollectionsResponse extends ToolResponse { creator: { email: string; id: string } | null }> total: number + after: string | null + before: string | null } } @@ -299,6 +326,8 @@ export interface HexListDataConnectionsParams { limit?: number sortBy?: string sortDirection?: string + after?: string + before?: string } export interface HexListDataConnectionsResponse extends ToolResponse { @@ -313,6 +342,8 @@ export interface HexListDataConnectionsResponse extends ToolResponse { allowWritebackCells: boolean | null }> total: number + after: string | null + before: string | null } } @@ -338,6 +369,8 @@ export interface HexListGroupsParams { limit?: number sortBy?: string sortDirection?: string + after?: string + before?: string } export interface HexListGroupsResponse extends ToolResponse { @@ -348,6 +381,8 @@ export interface HexListGroupsResponse extends ToolResponse { createdAt: string | null }> total: number + after: string | null + before: string | null } } @@ -410,6 +445,76 @@ export interface HexCreateCollectionResponse extends ToolResponse { } } +export interface HexUpdateCollectionParams { + apiKey: string + collectionId: string + name?: string + description?: string +} + +export interface HexUpdateCollectionResponse extends ToolResponse { + output: { + id: string + name: string + description: string | null + creator: { email: string; id: string } | null + } +} + +export interface HexCreateGroupParams { + apiKey: string + name: string + memberUserIds?: string +} + +export interface HexCreateGroupResponse extends ToolResponse { + output: { + id: string + name: string + createdAt: string | null + } +} + +export interface HexUpdateGroupParams { + apiKey: string + groupId: string + name?: string + addUserIds?: string + removeUserIds?: string +} + +export interface HexUpdateGroupResponse extends ToolResponse { + output: { + id: string + name: string + createdAt: string | null + } +} + +export interface HexDeleteGroupParams { + apiKey: string + groupId: string +} + +export interface HexDeleteGroupResponse extends ToolResponse { + output: { + success: boolean + groupId: string + } +} + +export interface HexDeactivateUserParams { + apiKey: string + userId: string +} + +export interface HexDeactivateUserResponse extends ToolResponse { + output: { + success: boolean + userId: string + } +} + export type HexResponse = | HexListProjectsResponse | HexGetProjectResponse @@ -427,3 +532,8 @@ export type HexResponse = | HexGetDataConnectionResponse | HexGetCollectionResponse | HexCreateCollectionResponse + | HexUpdateCollectionResponse + | HexCreateGroupResponse + | HexUpdateGroupResponse + | HexDeleteGroupResponse + | HexDeactivateUserResponse diff --git a/apps/sim/tools/hex/update_collection.ts b/apps/sim/tools/hex/update_collection.ts new file mode 100644 index 00000000000..a21c2aee1db --- /dev/null +++ b/apps/sim/tools/hex/update_collection.ts @@ -0,0 +1,85 @@ +import type { HexUpdateCollectionParams, HexUpdateCollectionResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const updateCollectionTool: ToolConfig< + HexUpdateCollectionParams, + HexUpdateCollectionResponse +> = { + id: 'hex_update_collection', + name: 'Hex Update Collection', + description: 'Update the name or description of an existing Hex collection.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + collectionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the collection to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the collection', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New description for the collection', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/collections/${params.collectionId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.name) body.name = params.name + if (params.description !== undefined) body.description = params.description + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + description: data.description ?? null, + creator: data.creator + ? { email: data.creator.email ?? null, id: data.creator.id ?? null } + : null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Collection UUID' }, + name: { type: 'string', description: 'Collection name' }, + description: { type: 'string', description: 'Collection description', optional: true }, + creator: { + type: 'object', + description: 'Collection creator', + optional: true, + properties: { + email: { type: 'string', description: 'Creator email' }, + id: { type: 'string', description: 'Creator UUID' }, + }, + }, + }, +} diff --git a/apps/sim/tools/hex/update_group.ts b/apps/sim/tools/hex/update_group.ts new file mode 100644 index 00000000000..d02f48c5a10 --- /dev/null +++ b/apps/sim/tools/hex/update_group.ts @@ -0,0 +1,106 @@ +import type { HexUpdateGroupParams, HexUpdateGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const updateGroupTool: ToolConfig = { + id: 'hex_update_group', + name: 'Hex Update Group', + description: 'Rename a Hex group or add/remove members from it.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + groupId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the group to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the group', + }, + addUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of user UUIDs to add to the group (e.g., ["uuid1", "uuid2"])', + }, + removeUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of user UUIDs to remove from the group (e.g., ["uuid1", "uuid2"])', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.name) body.name = params.name + + const parseIds = (value: unknown): string[] => { + let parsed: unknown + try { + parsed = typeof value === 'string' ? JSON.parse(value) : value + } catch { + throw new Error( + 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' + ) + } + if (!Array.isArray(parsed) || !parsed.every((id) => typeof id === 'string')) { + throw new Error( + 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' + ) + } + return parsed + } + + if (params.addUserIds || params.removeUserIds) { + const members: Record = {} + if (params.addUserIds) { + members.add = { users: parseIds(params.addUserIds).map((id) => ({ id: id.trim() })) } + } + if (params.removeUserIds) { + members.remove = { + users: parseIds(params.removeUserIds).map((id) => ({ id: id.trim() })), + } + } + body.members = members + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + createdAt: data.createdAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Group UUID' }, + name: { type: 'string', description: 'Group name' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + }, +} diff --git a/apps/sim/tools/hex/update_project.ts b/apps/sim/tools/hex/update_project.ts index e8da0a3c278..faf75cfb9a7 100644 --- a/apps/sim/tools/hex/update_project.ts +++ b/apps/sim/tools/hex/update_project.ts @@ -30,7 +30,7 @@ export const updateProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index d3826f8c2db..2ba0dbd97be 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1555,6 +1555,9 @@ import { guardrailsValidateTool } from '@/tools/guardrails' import { hexCancelRunTool, hexCreateCollectionTool, + hexCreateGroupTool, + hexDeactivateUserTool, + hexDeleteGroupTool, hexGetCollectionTool, hexGetDataConnectionTool, hexGetGroupTool, @@ -1568,6 +1571,8 @@ import { hexListProjectsTool, hexListUsersTool, hexRunProjectTool, + hexUpdateCollectionTool, + hexUpdateGroupTool, hexUpdateProjectTool, } from '@/tools/hex' import { httpRequestTool, webhookRequestTool } from '@/tools/http' @@ -4637,6 +4642,9 @@ export const tools: Record = { guardrails_validate: guardrailsValidateTool, hex_cancel_run: hexCancelRunTool, hex_create_collection: hexCreateCollectionTool, + hex_create_group: hexCreateGroupTool, + hex_deactivate_user: hexDeactivateUserTool, + hex_delete_group: hexDeleteGroupTool, hex_get_collection: hexGetCollectionTool, hex_get_data_connection: hexGetDataConnectionTool, hex_get_group: hexGetGroupTool, @@ -4650,6 +4658,8 @@ export const tools: Record = { hex_list_projects: hexListProjectsTool, hex_list_users: hexListUsersTool, hex_run_project: hexRunProjectTool, + hex_update_collection: hexUpdateCollectionTool, + hex_update_group: hexUpdateGroupTool, hex_update_project: hexUpdateProjectTool, instantly_activate_campaign: instantlyActivateCampaignTool, instantly_create_campaign: instantlyCreateCampaignTool,