mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(tools): added clerk tools and block (#3032)
* feat(tools): added clerk tools and block * updated docs gen script * use clerk api types
This commit is contained in:
393
apps/sim/blocks/blocks/clerk.ts
Normal file
393
apps/sim/blocks/blocks/clerk.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import { ClerkIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import type { ClerkResponse } from '@/tools/clerk/types'
|
||||
|
||||
export const ClerkBlock: BlockConfig<ClerkResponse> = {
|
||||
type: 'clerk',
|
||||
name: 'Clerk',
|
||||
description: 'Manage users, organizations, and sessions in Clerk',
|
||||
longDescription:
|
||||
'Integrate Clerk authentication and user management into your workflow. Create, update, delete, and list users. Manage organizations and their memberships. Monitor and control user sessions.',
|
||||
docsLink: 'https://docs.sim.ai/tools/clerk',
|
||||
category: 'tools',
|
||||
bgColor: '#131316',
|
||||
icon: ClerkIcon,
|
||||
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'List Users', id: 'clerk_list_users' },
|
||||
{ label: 'Get User', id: 'clerk_get_user' },
|
||||
{ label: 'Create User', id: 'clerk_create_user' },
|
||||
{ label: 'Update User', id: 'clerk_update_user' },
|
||||
{ label: 'Delete User', id: 'clerk_delete_user' },
|
||||
{ label: 'List Organizations', id: 'clerk_list_organizations' },
|
||||
{ label: 'Get Organization', id: 'clerk_get_organization' },
|
||||
{ label: 'Create Organization', id: 'clerk_create_organization' },
|
||||
{ label: 'List Sessions', id: 'clerk_list_sessions' },
|
||||
{ label: 'Get Session', id: 'clerk_get_session' },
|
||||
{ label: 'Revoke Session', id: 'clerk_revoke_session' },
|
||||
],
|
||||
value: () => 'clerk_list_users',
|
||||
},
|
||||
{
|
||||
id: 'secretKey',
|
||||
title: 'Secret Key',
|
||||
type: 'short-input',
|
||||
password: true,
|
||||
placeholder: 'sk_live_... or sk_test_...',
|
||||
required: true,
|
||||
},
|
||||
// List Users params
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Search Query',
|
||||
type: 'short-input',
|
||||
placeholder: 'Search by email, phone, username, or name',
|
||||
condition: { field: 'operation', value: 'clerk_list_users' },
|
||||
},
|
||||
{
|
||||
id: 'emailAddressFilter',
|
||||
title: 'Email Filter',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by email (comma-separated)',
|
||||
condition: { field: 'operation', value: 'clerk_list_users' },
|
||||
},
|
||||
{
|
||||
id: 'usernameFilter',
|
||||
title: 'Username Filter',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by username (comma-separated)',
|
||||
condition: { field: 'operation', value: 'clerk_list_users' },
|
||||
},
|
||||
// Get User params
|
||||
{
|
||||
id: 'userId',
|
||||
title: 'User ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'user_...',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['clerk_get_user', 'clerk_update_user', 'clerk_delete_user'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['clerk_get_user', 'clerk_update_user', 'clerk_delete_user'],
|
||||
},
|
||||
},
|
||||
// Create/Update User params
|
||||
{
|
||||
id: 'emailAddress',
|
||||
title: 'Email Address',
|
||||
type: 'short-input',
|
||||
placeholder: 'user@example.com (comma-separated for multiple)',
|
||||
condition: { field: 'operation', value: 'clerk_create_user' },
|
||||
},
|
||||
{
|
||||
id: 'phoneNumber',
|
||||
title: 'Phone Number',
|
||||
type: 'short-input',
|
||||
placeholder: '+1234567890 (comma-separated for multiple)',
|
||||
condition: { field: 'operation', value: 'clerk_create_user' },
|
||||
},
|
||||
{
|
||||
id: 'username',
|
||||
title: 'Username',
|
||||
type: 'short-input',
|
||||
placeholder: 'johndoe',
|
||||
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
|
||||
},
|
||||
{
|
||||
id: 'password',
|
||||
title: 'Password',
|
||||
type: 'short-input',
|
||||
password: true,
|
||||
placeholder: 'Minimum 8 characters',
|
||||
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
|
||||
},
|
||||
{
|
||||
id: 'firstName',
|
||||
title: 'First Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'John',
|
||||
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
|
||||
},
|
||||
{
|
||||
id: 'lastName',
|
||||
title: 'Last Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Doe',
|
||||
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
|
||||
},
|
||||
{
|
||||
id: 'externalId',
|
||||
title: 'External ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Your system user ID',
|
||||
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
|
||||
},
|
||||
{
|
||||
id: 'publicMetadata',
|
||||
title: 'Public Metadata',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder: '{"role": "admin"}',
|
||||
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
|
||||
},
|
||||
{
|
||||
id: 'privateMetadata',
|
||||
title: 'Private Metadata',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder: '{"internalId": "123"}',
|
||||
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
|
||||
},
|
||||
// Organization params
|
||||
{
|
||||
id: 'orgQuery',
|
||||
title: 'Search Query',
|
||||
type: 'short-input',
|
||||
placeholder: 'Search by name, ID, or slug',
|
||||
condition: { field: 'operation', value: 'clerk_list_organizations' },
|
||||
},
|
||||
{
|
||||
id: 'includeMembersCount',
|
||||
title: 'Include Members Count',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'clerk_list_organizations' },
|
||||
},
|
||||
{
|
||||
id: 'organizationId',
|
||||
title: 'Organization ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'org_... or slug',
|
||||
condition: { field: 'operation', value: 'clerk_get_organization' },
|
||||
required: { field: 'operation', value: 'clerk_get_organization' },
|
||||
},
|
||||
{
|
||||
id: 'orgName',
|
||||
title: 'Organization Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Acme Corp',
|
||||
condition: { field: 'operation', value: 'clerk_create_organization' },
|
||||
required: { field: 'operation', value: 'clerk_create_organization' },
|
||||
},
|
||||
{
|
||||
id: 'createdBy',
|
||||
title: 'Creator User ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'user_... (will become admin)',
|
||||
condition: { field: 'operation', value: 'clerk_create_organization' },
|
||||
required: { field: 'operation', value: 'clerk_create_organization' },
|
||||
},
|
||||
{
|
||||
id: 'slug',
|
||||
title: 'Slug',
|
||||
type: 'short-input',
|
||||
placeholder: 'acme-corp',
|
||||
condition: { field: 'operation', value: 'clerk_create_organization' },
|
||||
},
|
||||
{
|
||||
id: 'maxAllowedMemberships',
|
||||
title: 'Max Members',
|
||||
type: 'short-input',
|
||||
placeholder: '0 for unlimited',
|
||||
condition: { field: 'operation', value: 'clerk_create_organization' },
|
||||
},
|
||||
// Session params
|
||||
{
|
||||
id: 'sessionUserId',
|
||||
title: 'User ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'user_...',
|
||||
condition: { field: 'operation', value: 'clerk_list_sessions' },
|
||||
},
|
||||
{
|
||||
id: 'clientId',
|
||||
title: 'Client ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'client_...',
|
||||
condition: { field: 'operation', value: 'clerk_list_sessions' },
|
||||
},
|
||||
{
|
||||
id: 'sessionStatus',
|
||||
title: 'Status',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: 'Active', id: 'active' },
|
||||
{ label: 'Ended', id: 'ended' },
|
||||
{ label: 'Expired', id: 'expired' },
|
||||
{ label: 'Revoked', id: 'revoked' },
|
||||
{ label: 'Abandoned', id: 'abandoned' },
|
||||
{ label: 'Pending', id: 'pending' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: 'clerk_list_sessions' },
|
||||
},
|
||||
{
|
||||
id: 'sessionId',
|
||||
title: 'Session ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'sess_...',
|
||||
condition: { field: 'operation', value: ['clerk_get_session', 'clerk_revoke_session'] },
|
||||
required: { field: 'operation', value: ['clerk_get_session', 'clerk_revoke_session'] },
|
||||
},
|
||||
// Pagination params (common)
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: 'Results per page (1-500, default: 10)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['clerk_list_users', 'clerk_list_organizations', 'clerk_list_sessions'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
placeholder: 'Skip N results for pagination',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['clerk_list_users', 'clerk_list_organizations', 'clerk_list_sessions'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
tools: {
|
||||
access: [
|
||||
'clerk_list_users',
|
||||
'clerk_get_user',
|
||||
'clerk_create_user',
|
||||
'clerk_update_user',
|
||||
'clerk_delete_user',
|
||||
'clerk_list_organizations',
|
||||
'clerk_get_organization',
|
||||
'clerk_create_organization',
|
||||
'clerk_list_sessions',
|
||||
'clerk_get_session',
|
||||
'clerk_revoke_session',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => params.operation as string,
|
||||
params: (params) => {
|
||||
const {
|
||||
operation,
|
||||
secretKey,
|
||||
emailAddressFilter,
|
||||
usernameFilter,
|
||||
orgQuery,
|
||||
orgName,
|
||||
sessionUserId,
|
||||
sessionStatus,
|
||||
publicMetadata,
|
||||
privateMetadata,
|
||||
maxAllowedMemberships,
|
||||
...rest
|
||||
} = params
|
||||
|
||||
const cleanParams: Record<string, unknown> = {
|
||||
secretKey,
|
||||
}
|
||||
|
||||
// Map UI fields to API params based on operation
|
||||
switch (operation) {
|
||||
case 'clerk_list_users':
|
||||
if (emailAddressFilter) cleanParams.emailAddress = emailAddressFilter
|
||||
if (usernameFilter) cleanParams.username = usernameFilter
|
||||
break
|
||||
case 'clerk_create_user':
|
||||
case 'clerk_update_user':
|
||||
if (publicMetadata) {
|
||||
cleanParams.publicMetadata =
|
||||
typeof publicMetadata === 'string' ? JSON.parse(publicMetadata) : publicMetadata
|
||||
}
|
||||
if (privateMetadata) {
|
||||
cleanParams.privateMetadata =
|
||||
typeof privateMetadata === 'string' ? JSON.parse(privateMetadata) : privateMetadata
|
||||
}
|
||||
break
|
||||
case 'clerk_list_organizations':
|
||||
if (orgQuery) cleanParams.query = orgQuery
|
||||
break
|
||||
case 'clerk_create_organization':
|
||||
if (orgName) cleanParams.name = orgName
|
||||
if (maxAllowedMemberships)
|
||||
cleanParams.maxAllowedMemberships = Number(maxAllowedMemberships)
|
||||
break
|
||||
case 'clerk_list_sessions':
|
||||
if (sessionUserId) cleanParams.userId = sessionUserId
|
||||
if (sessionStatus) cleanParams.status = sessionStatus
|
||||
break
|
||||
}
|
||||
|
||||
// Add remaining params that don't need mapping
|
||||
Object.entries(rest).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
cleanParams[key] = value
|
||||
}
|
||||
})
|
||||
|
||||
return cleanParams
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
secretKey: { type: 'string', description: 'Clerk Secret Key' },
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
organizationId: { type: 'string', description: 'Organization ID or slug' },
|
||||
sessionId: { type: 'string', description: 'Session ID' },
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
limit: { type: 'number', description: 'Results per page' },
|
||||
offset: { type: 'number', description: 'Pagination offset' },
|
||||
},
|
||||
|
||||
outputs: {
|
||||
// List outputs (arrays stored as json for block compatibility)
|
||||
users: { type: 'json', description: 'Array of user objects' },
|
||||
organizations: { type: 'json', description: 'Array of organization objects' },
|
||||
sessions: { type: 'json', description: 'Array of session objects' },
|
||||
// Single entity fields (destructured from get/create/update operations)
|
||||
id: { type: 'string', description: 'Resource ID (user, organization, or session)' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug' },
|
||||
username: { type: 'string', description: 'Username' },
|
||||
firstName: { type: 'string', description: 'First name' },
|
||||
lastName: { type: 'string', description: 'Last name' },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL' },
|
||||
hasImage: { type: 'boolean', description: 'Whether resource has an image' },
|
||||
emailAddresses: { type: 'json', description: 'User email addresses' },
|
||||
phoneNumbers: { type: 'json', description: 'User phone numbers' },
|
||||
primaryEmailAddressId: { type: 'string', description: 'Primary email address ID' },
|
||||
primaryPhoneNumberId: { type: 'string', description: 'Primary phone number ID' },
|
||||
externalId: { type: 'string', description: 'External system ID' },
|
||||
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
|
||||
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
|
||||
banned: { type: 'boolean', description: 'Whether user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether user is locked' },
|
||||
userId: { type: 'string', description: 'User ID (for sessions)' },
|
||||
clientId: { type: 'string', description: 'Client ID (for sessions)' },
|
||||
status: { type: 'string', description: 'Session status' },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp' },
|
||||
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp' },
|
||||
membersCount: { type: 'number', description: 'Number of members' },
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
// Common outputs
|
||||
totalCount: { type: 'number', description: 'Total count for paginated results' },
|
||||
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
|
||||
object: { type: 'string', description: 'Object type' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { CalendlyBlock } from '@/blocks/blocks/calendly'
|
||||
import { ChatTriggerBlock } from '@/blocks/blocks/chat_trigger'
|
||||
import { CirclebackBlock } from '@/blocks/blocks/circleback'
|
||||
import { ClayBlock } from '@/blocks/blocks/clay'
|
||||
import { ClerkBlock } from '@/blocks/blocks/clerk'
|
||||
import { ConditionBlock } from '@/blocks/blocks/condition'
|
||||
import { ConfluenceBlock, ConfluenceV2Block } from '@/blocks/blocks/confluence'
|
||||
import { CursorBlock, CursorV2Block } from '@/blocks/blocks/cursor'
|
||||
@@ -168,6 +169,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
chat_trigger: ChatTriggerBlock,
|
||||
circleback: CirclebackBlock,
|
||||
clay: ClayBlock,
|
||||
clerk: ClerkBlock,
|
||||
condition: ConditionBlock,
|
||||
confluence: ConfluenceBlock,
|
||||
confluence_v2: ConfluenceV2Block,
|
||||
|
||||
@@ -2096,6 +2096,23 @@ export function ClayIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function ClerkIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox='0 0 128 128' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<circle cx='64' cy='64' r='20' fill='white' />
|
||||
<path
|
||||
d='M99.5716 10.788C101.571 12.1272 101.742 14.9444 100.04 16.646L85.4244 31.2618C84.1035 32.5828 82.0542 32.7914 80.3915 31.9397C75.4752 29.421 69.9035 28 64 28C44.1177 28 28 44.1177 28 64C28 69.9035 29.421 75.4752 31.9397 80.3915C32.7914 82.0542 32.5828 84.1035 31.2618 85.4244L16.646 100.04C14.9444 101.742 12.1272 101.571 10.788 99.5716C3.97411 89.3989 0 77.1635 0 64C0 28.6538 28.6538 0 64 0C77.1635 0 89.3989 3.97411 99.5716 10.788Z'
|
||||
fill='white'
|
||||
fillOpacity='0.4'
|
||||
/>
|
||||
<path
|
||||
d='M100.04 111.354C101.742 113.056 101.571 115.873 99.5717 117.212C89.3989 124.026 77.1636 128 64 128C50.8364 128 38.6011 124.026 28.4283 117.212C26.4289 115.873 26.2581 113.056 27.9597 111.354L42.5755 96.7382C43.8965 95.4172 45.9457 95.2085 47.6084 96.0603C52.5248 98.579 58.0964 100 64 100C69.9036 100 75.4753 98.579 80.3916 96.0603C82.0543 95.2085 84.1036 95.4172 85.4245 96.7382L100.04 111.354Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function MicrosoftIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 23 23' {...props}>
|
||||
|
||||
146
apps/sim/tools/clerk/create_organization.ts
Normal file
146
apps/sim/tools/clerk/create_organization.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkCreateOrganizationParams,
|
||||
ClerkCreateOrganizationResponse,
|
||||
ClerkOrganization,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateOrganization')
|
||||
|
||||
export const clerkCreateOrganizationTool: ToolConfig<
|
||||
ClerkCreateOrganizationParams,
|
||||
ClerkCreateOrganizationResponse
|
||||
> = {
|
||||
id: 'clerk_create_organization',
|
||||
name: 'Create Organization in Clerk',
|
||||
description: 'Create a new organization in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the organization',
|
||||
},
|
||||
createdBy: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID of the creator (will become admin)',
|
||||
},
|
||||
slug: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Slug identifier for the organization',
|
||||
},
|
||||
maxAllowedMemberships: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum member capacity (0 for unlimited)',
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public metadata (JSON object)',
|
||||
},
|
||||
privateMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Private metadata (JSON object)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/organizations',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
created_by: params.createdBy,
|
||||
}
|
||||
|
||||
if (params.slug) body.slug = params.slug
|
||||
if (params.maxAllowedMemberships !== undefined)
|
||||
body.max_allowed_memberships = params.maxAllowedMemberships
|
||||
if (params.publicMetadata) body.public_metadata = params.publicMetadata
|
||||
if (params.privateMetadata) body.private_metadata = params.privateMetadata
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganization | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to create organization in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const org = data as ClerkOrganization
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
slug: org.slug ?? null,
|
||||
imageUrl: org.image_url ?? null,
|
||||
hasImage: org.has_image ?? false,
|
||||
membersCount: org.members_count ?? null,
|
||||
pendingInvitationsCount: org.pending_invitations_count ?? null,
|
||||
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
|
||||
adminDeleteEnabled: org.admin_delete_enabled ?? false,
|
||||
createdBy: org.created_by ?? null,
|
||||
createdAt: org.created_at,
|
||||
updatedAt: org.updated_at,
|
||||
publicMetadata: org.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Created organization ID' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
|
||||
membersCount: { type: 'number', description: 'Number of members', optional: true },
|
||||
pendingInvitationsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of pending invitations',
|
||||
optional: true,
|
||||
},
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
227
apps/sim/tools/clerk/create_user.ts
Normal file
227
apps/sim/tools/clerk/create_user.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkCreateUserParams,
|
||||
ClerkCreateUserResponse,
|
||||
ClerkEmailAddress,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateUser')
|
||||
|
||||
export const clerkCreateUserTool: ToolConfig<ClerkCreateUserParams, ClerkCreateUserResponse> = {
|
||||
id: 'clerk_create_user',
|
||||
name: 'Create User in Clerk',
|
||||
description: 'Create a new user in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
emailAddress: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email addresses for the user (comma-separated for multiple)',
|
||||
},
|
||||
phoneNumber: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Phone numbers for the user (comma-separated for multiple)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Username for the user (must be unique)',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Password for the user (minimum 8 characters)',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'First name of the user',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last name of the user',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'External system identifier (must be unique)',
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public metadata (JSON object, readable from frontend)',
|
||||
},
|
||||
privateMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Private metadata (JSON object, backend only)',
|
||||
},
|
||||
unsafeMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unsafe metadata (JSON object, modifiable from frontend)',
|
||||
},
|
||||
skipPasswordChecks: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip password validation checks',
|
||||
},
|
||||
skipPasswordRequirement: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Make password optional',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/users',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
|
||||
if (params.emailAddress) {
|
||||
const emailStr = params.emailAddress as string
|
||||
body.email_address = emailStr.split(',').map((e) => e.trim())
|
||||
}
|
||||
if (params.phoneNumber) {
|
||||
const phoneStr = params.phoneNumber as string
|
||||
body.phone_number = phoneStr.split(',').map((p) => p.trim())
|
||||
}
|
||||
if (params.username) body.username = params.username.trim()
|
||||
if (params.password) body.password = params.password
|
||||
if (params.firstName) body.first_name = params.firstName.trim()
|
||||
if (params.lastName) body.last_name = params.lastName.trim()
|
||||
if (params.externalId) body.external_id = params.externalId.trim()
|
||||
if (params.publicMetadata) body.public_metadata = params.publicMetadata
|
||||
if (params.privateMetadata) body.private_metadata = params.privateMetadata
|
||||
if (params.unsafeMetadata) body.unsafe_metadata = params.unsafeMetadata
|
||||
if (params.skipPasswordChecks !== undefined)
|
||||
body.skip_password_checks = params.skipPasswordChecks
|
||||
if (params.skipPasswordRequirement !== undefined)
|
||||
body.skip_password_requirement = params.skipPasswordRequirement
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to create user in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
verified: email.verification?.status === 'verified',
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
verified: phone.verification?.status === 'verified',
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Created user ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
verified: { type: 'boolean', description: 'Whether email is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
verified: { type: 'boolean', description: 'Whether phone is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
75
apps/sim/tools/clerk/delete_user.ts
Normal file
75
apps/sim/tools/clerk/delete_user.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkDeleteResponse,
|
||||
ClerkDeleteUserParams,
|
||||
ClerkDeleteUserResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkDeleteUser')
|
||||
|
||||
export const clerkDeleteUserTool: ToolConfig<ClerkDeleteUserParams, ClerkDeleteUserResponse> = {
|
||||
id: 'clerk_delete_user',
|
||||
name: 'Delete User from Clerk',
|
||||
description: 'Delete a user from your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkDeleteResponse | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to delete user from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const deleteData = data as ClerkDeleteResponse
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: deleteData.id,
|
||||
object: deleteData.object ?? 'user',
|
||||
deleted: deleteData.deleted ?? true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Deleted user ID' },
|
||||
object: { type: 'string', description: 'Object type (user)' },
|
||||
deleted: { type: 'boolean', description: 'Whether the user was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
102
apps/sim/tools/clerk/get_organization.ts
Normal file
102
apps/sim/tools/clerk/get_organization.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkGetOrganizationParams,
|
||||
ClerkGetOrganizationResponse,
|
||||
ClerkOrganization,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetOrganization')
|
||||
|
||||
export const clerkGetOrganizationTool: ToolConfig<
|
||||
ClerkGetOrganizationParams,
|
||||
ClerkGetOrganizationResponse
|
||||
> = {
|
||||
id: 'clerk_get_organization',
|
||||
name: 'Get Organization from Clerk',
|
||||
description: 'Retrieve a single organization by ID or slug from Clerk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID or slug of the organization to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganization | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get organization from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const org = data as ClerkOrganization
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
slug: org.slug ?? null,
|
||||
imageUrl: org.image_url ?? null,
|
||||
hasImage: org.has_image ?? false,
|
||||
membersCount: org.members_count ?? null,
|
||||
pendingInvitationsCount: org.pending_invitations_count ?? null,
|
||||
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
|
||||
adminDeleteEnabled: org.admin_delete_enabled ?? false,
|
||||
createdBy: org.created_by ?? null,
|
||||
createdAt: org.created_at,
|
||||
updatedAt: org.updated_at,
|
||||
publicMetadata: org.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Organization ID' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
|
||||
membersCount: { type: 'number', description: 'Number of members', optional: true },
|
||||
pendingInvitationsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of pending invitations',
|
||||
optional: true,
|
||||
},
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
93
apps/sim/tools/clerk/get_session.ts
Normal file
93
apps/sim/tools/clerk/get_session.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkGetSessionParams,
|
||||
ClerkGetSessionResponse,
|
||||
ClerkSession,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetSession')
|
||||
|
||||
export const clerkGetSessionTool: ToolConfig<ClerkGetSessionParams, ClerkGetSessionResponse> = {
|
||||
id: 'clerk_get_session',
|
||||
name: 'Get Session from Clerk',
|
||||
description: 'Retrieve a single session by ID from Clerk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the session to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkSession | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get session from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const session = data as ClerkSession
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
clientId: session.client_id,
|
||||
status: session.status,
|
||||
lastActiveAt: session.last_active_at ?? null,
|
||||
lastActiveOrganizationId: session.last_active_organization_id ?? null,
|
||||
expireAt: session.expire_at ?? null,
|
||||
abandonAt: session.abandon_at ?? null,
|
||||
createdAt: session.created_at,
|
||||
updatedAt: session.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Session ID' },
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
clientId: { type: 'string', description: 'Client ID' },
|
||||
status: { type: 'string', description: 'Session status' },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
lastActiveOrganizationId: {
|
||||
type: 'string',
|
||||
description: 'Last active organization ID',
|
||||
optional: true,
|
||||
},
|
||||
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
167
apps/sim/tools/clerk/get_user.ts
Normal file
167
apps/sim/tools/clerk/get_user.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkEmailAddress,
|
||||
ClerkGetUserParams,
|
||||
ClerkGetUserResponse,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetUser')
|
||||
|
||||
export const clerkGetUserTool: ToolConfig<ClerkGetUserParams, ClerkGetUserResponse> = {
|
||||
id: 'clerk_get_user',
|
||||
name: 'Get User from Clerk',
|
||||
description: 'Retrieve a single user by their ID from Clerk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get user from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
hasImage: user.has_image ?? false,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
primaryWeb3WalletId: user.primary_web3_wallet_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
verified: email.verification?.status === 'verified',
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
verified: phone.verification?.status === 'verified',
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
passwordEnabled: user.password_enabled ?? false,
|
||||
twoFactorEnabled: user.two_factor_enabled ?? false,
|
||||
totpEnabled: user.totp_enabled ?? false,
|
||||
backupCodeEnabled: user.backup_code_enabled ?? false,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
deleteSelfEnabled: user.delete_self_enabled ?? false,
|
||||
createOrganizationEnabled: user.create_organization_enabled ?? false,
|
||||
lastSignInAt: user.last_sign_in_at ?? null,
|
||||
lastActiveAt: user.last_active_at ?? null,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
privateMetadata: user.private_metadata ?? {},
|
||||
unsafeMetadata: user.unsafe_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether user has a profile image' },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryWeb3WalletId: { type: 'string', description: 'Primary Web3 wallet ID', optional: true },
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
verified: { type: 'boolean', description: 'Whether email is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
verified: { type: 'boolean', description: 'Whether phone is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
|
||||
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
|
||||
totpEnabled: { type: 'boolean', description: 'Whether TOTP is enabled' },
|
||||
backupCodeEnabled: { type: 'boolean', description: 'Whether backup codes are enabled' },
|
||||
banned: { type: 'boolean', description: 'Whether user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether user is locked' },
|
||||
deleteSelfEnabled: { type: 'boolean', description: 'Whether user can delete themselves' },
|
||||
createOrganizationEnabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether user can create organizations',
|
||||
},
|
||||
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp', optional: true },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata (readable from frontend)' },
|
||||
privateMetadata: { type: 'json', description: 'Private metadata (backend only)' },
|
||||
unsafeMetadata: { type: 'json', description: 'Unsafe metadata (modifiable from frontend)' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
11
apps/sim/tools/clerk/index.ts
Normal file
11
apps/sim/tools/clerk/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { clerkCreateOrganizationTool } from './create_organization'
|
||||
export { clerkCreateUserTool } from './create_user'
|
||||
export { clerkDeleteUserTool } from './delete_user'
|
||||
export { clerkGetOrganizationTool } from './get_organization'
|
||||
export { clerkGetSessionTool } from './get_session'
|
||||
export { clerkGetUserTool } from './get_user'
|
||||
export { clerkListOrganizationsTool } from './list_organizations'
|
||||
export { clerkListSessionsTool } from './list_sessions'
|
||||
export { clerkListUsersTool } from './list_users'
|
||||
export { clerkRevokeSessionTool } from './revoke_session'
|
||||
export { clerkUpdateUserTool } from './update_user'
|
||||
157
apps/sim/tools/clerk/list_organizations.ts
Normal file
157
apps/sim/tools/clerk/list_organizations.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkListOrganizationsParams,
|
||||
ClerkListOrganizationsResponse,
|
||||
ClerkOrganization,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListOrganizations')
|
||||
|
||||
export const clerkListOrganizationsTool: ToolConfig<
|
||||
ClerkListOrganizationsParams,
|
||||
ClerkListOrganizationsResponse
|
||||
> = {
|
||||
id: 'clerk_list_organizations',
|
||||
name: 'List Organizations from Clerk',
|
||||
description: 'List all organizations in your Clerk application with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination',
|
||||
},
|
||||
includeMembersCount: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include member count for each organization',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search by organization ID, name, or slug',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort field (name, created_at, members_count) with +/- prefix',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
if (params.includeMembersCount) queryParams.append('include_members_count', 'true')
|
||||
if (params.query) queryParams.append('query', params.query)
|
||||
if (params.orderBy) queryParams.append('order_by', params.orderBy)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://api.clerk.com/v1/organizations?${queryString}`
|
||||
: 'https://api.clerk.com/v1/organizations'
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const json: { data: ClerkOrganization[]; total_count: number } | ClerkApiError =
|
||||
await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data: json, status: response.status })
|
||||
throw new Error(
|
||||
(json as ClerkApiError).errors?.[0]?.message || 'Failed to list organizations from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const responseData = json as { data: ClerkOrganization[]; total_count: number }
|
||||
|
||||
// Transform each organization to extract key fields
|
||||
const organizations = responseData.data.map((org) => ({
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
slug: org.slug ?? null,
|
||||
imageUrl: org.image_url ?? null,
|
||||
hasImage: org.has_image ?? false,
|
||||
membersCount: org.members_count ?? null,
|
||||
pendingInvitationsCount: org.pending_invitations_count ?? null,
|
||||
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
|
||||
adminDeleteEnabled: org.admin_delete_enabled ?? false,
|
||||
createdBy: org.created_by ?? null,
|
||||
createdAt: org.created_at,
|
||||
updatedAt: org.updated_at,
|
||||
publicMetadata: org.public_metadata ?? {},
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
organizations,
|
||||
totalCount: responseData.total_count ?? organizations.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organizations: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk organization objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Organization ID' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
|
||||
membersCount: { type: 'number', description: 'Number of members', optional: true },
|
||||
pendingInvitationsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of pending invitations',
|
||||
optional: true,
|
||||
},
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of organizations' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
152
apps/sim/tools/clerk/list_sessions.ts
Normal file
152
apps/sim/tools/clerk/list_sessions.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkListSessionsParams,
|
||||
ClerkListSessionsResponse,
|
||||
ClerkSession,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListSessions')
|
||||
|
||||
export const clerkListSessionsTool: ToolConfig<ClerkListSessionsParams, ClerkListSessionsResponse> =
|
||||
{
|
||||
id: 'clerk_list_sessions',
|
||||
name: 'List Sessions from Clerk',
|
||||
description: 'List sessions for a user or client in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to list sessions for (required if clientId not provided)',
|
||||
},
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Client ID to list sessions for (required if userId not provided)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by session status (abandoned, active, ended, expired, pending, removed, replaced, revoked)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.userId) queryParams.append('user_id', params.userId)
|
||||
if (params.clientId) queryParams.append('client_id', params.clientId)
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://api.clerk.com/v1/sessions?${queryString}`
|
||||
: 'https://api.clerk.com/v1/sessions'
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkSession[] | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to list sessions from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const totalCount = Number.parseInt(response.headers.get('x-total-count') || '0', 10)
|
||||
|
||||
const sessions = (data as ClerkSession[]).map((session) => ({
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
clientId: session.client_id,
|
||||
status: session.status,
|
||||
lastActiveAt: session.last_active_at ?? null,
|
||||
lastActiveOrganizationId: session.last_active_organization_id ?? null,
|
||||
expireAt: session.expire_at ?? null,
|
||||
abandonAt: session.abandon_at ?? null,
|
||||
createdAt: session.created_at,
|
||||
updatedAt: session.updated_at,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sessions,
|
||||
totalCount: totalCount || sessions.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sessions: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk session objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Session ID' },
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
clientId: { type: 'string', description: 'Client ID' },
|
||||
status: { type: 'string', description: 'Session status' },
|
||||
lastActiveAt: {
|
||||
type: 'number',
|
||||
description: 'Last activity timestamp',
|
||||
optional: true,
|
||||
},
|
||||
lastActiveOrganizationId: {
|
||||
type: 'string',
|
||||
description: 'Last active organization ID',
|
||||
optional: true,
|
||||
},
|
||||
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of sessions' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
250
apps/sim/tools/clerk/list_users.ts
Normal file
250
apps/sim/tools/clerk/list_users.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkEmailAddress,
|
||||
ClerkListUsersParams,
|
||||
ClerkListUsersResponse,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListUsers')
|
||||
|
||||
export const clerkListUsersTool: ToolConfig<ClerkListUsersParams, ClerkListUsersResponse> = {
|
||||
id: 'clerk_list_users',
|
||||
name: 'List Users from Clerk',
|
||||
description: 'List all users in your Clerk application with optional filtering and pagination',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort field with optional +/- prefix for direction (default: -created_at)',
|
||||
},
|
||||
emailAddress: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by email address (comma-separated for multiple)',
|
||||
},
|
||||
phoneNumber: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by phone number (comma-separated for multiple)',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by external ID (comma-separated for multiple)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by username (comma-separated for multiple)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by user ID (comma-separated for multiple)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search query to match across email, phone, username, and names',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
if (params.orderBy) queryParams.append('order_by', params.orderBy)
|
||||
if (params.query) queryParams.append('query', params.query)
|
||||
|
||||
// Handle comma-separated array params
|
||||
if (params.emailAddress) {
|
||||
params.emailAddress.split(',').forEach((email) => {
|
||||
queryParams.append('email_address', email.trim())
|
||||
})
|
||||
}
|
||||
if (params.phoneNumber) {
|
||||
params.phoneNumber.split(',').forEach((phone) => {
|
||||
queryParams.append('phone_number', phone.trim())
|
||||
})
|
||||
}
|
||||
if (params.externalId) {
|
||||
params.externalId.split(',').forEach((id) => {
|
||||
queryParams.append('external_id', id.trim())
|
||||
})
|
||||
}
|
||||
if (params.username) {
|
||||
params.username.split(',').forEach((uname) => {
|
||||
queryParams.append('username', uname.trim())
|
||||
})
|
||||
}
|
||||
if (params.userId) {
|
||||
params.userId.split(',').forEach((id) => {
|
||||
queryParams.append('user_id', id.trim())
|
||||
})
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://api.clerk.com/v1/users?${queryString}`
|
||||
: 'https://api.clerk.com/v1/users'
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser[] | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to list users from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
// The response is an array of users, total_count is in the header
|
||||
const totalCount = Number.parseInt(response.headers.get('x-total-count') || '0', 10)
|
||||
|
||||
// Transform each user to extract key fields
|
||||
const users = (data as ClerkUser[]).map((user) => ({
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
hasImage: user.has_image ?? false,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
passwordEnabled: user.password_enabled ?? false,
|
||||
twoFactorEnabled: user.two_factor_enabled ?? false,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
lastSignInAt: user.last_sign_in_at ?? null,
|
||||
lastActiveAt: user.last_active_at ?? null,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users,
|
||||
totalCount: totalCount || users.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk user objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether user has a profile image' },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
|
||||
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
|
||||
banned: { type: 'boolean', description: 'Whether user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether user is locked' },
|
||||
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp', optional: true },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of users matching the query' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
96
apps/sim/tools/clerk/revoke_session.ts
Normal file
96
apps/sim/tools/clerk/revoke_session.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkRevokeSessionParams,
|
||||
ClerkRevokeSessionResponse,
|
||||
ClerkSession,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkRevokeSession')
|
||||
|
||||
export const clerkRevokeSessionTool: ToolConfig<
|
||||
ClerkRevokeSessionParams,
|
||||
ClerkRevokeSessionResponse
|
||||
> = {
|
||||
id: 'clerk_revoke_session',
|
||||
name: 'Revoke Session in Clerk',
|
||||
description: 'Revoke a session to immediately invalidate it',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the session to revoke',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId}/revoke`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkSession | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to revoke session in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const session = data as ClerkSession
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
clientId: session.client_id,
|
||||
status: session.status,
|
||||
lastActiveAt: session.last_active_at ?? null,
|
||||
lastActiveOrganizationId: session.last_active_organization_id ?? null,
|
||||
expireAt: session.expire_at ?? null,
|
||||
abandonAt: session.abandon_at ?? null,
|
||||
createdAt: session.created_at,
|
||||
updatedAt: session.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Session ID' },
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
clientId: { type: 'string', description: 'Client ID' },
|
||||
status: { type: 'string', description: 'Session status (should be revoked)' },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
lastActiveOrganizationId: {
|
||||
type: 'string',
|
||||
description: 'Last active organization ID',
|
||||
optional: true,
|
||||
},
|
||||
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
552
apps/sim/tools/clerk/types.ts
Normal file
552
apps/sim/tools/clerk/types.ts
Normal file
@@ -0,0 +1,552 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Clerk API error response
|
||||
*/
|
||||
export interface ClerkApiError {
|
||||
errors?: { message: string }[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Clerk delete response
|
||||
*/
|
||||
export interface ClerkDeleteResponse {
|
||||
id: string
|
||||
object: string
|
||||
deleted: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Clerk User object
|
||||
*/
|
||||
export interface ClerkUser {
|
||||
id: string
|
||||
object: 'user'
|
||||
username: string | null
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
image_url: string
|
||||
has_image: boolean
|
||||
primary_email_address_id: string | null
|
||||
primary_phone_number_id: string | null
|
||||
primary_web3_wallet_id: string | null
|
||||
password_enabled: boolean
|
||||
two_factor_enabled: boolean
|
||||
totp_enabled: boolean
|
||||
backup_code_enabled: boolean
|
||||
email_addresses: ClerkEmailAddress[]
|
||||
phone_numbers: ClerkPhoneNumber[]
|
||||
web3_wallets: ClerkWeb3Wallet[]
|
||||
external_accounts: ClerkExternalAccount[]
|
||||
external_id: string | null
|
||||
last_sign_in_at: number | null
|
||||
banned: boolean
|
||||
locked: boolean
|
||||
lockout_expires_in_seconds: number | null
|
||||
verification_attempts_remaining: number | null
|
||||
created_at: number
|
||||
updated_at: number
|
||||
delete_self_enabled: boolean
|
||||
create_organization_enabled: boolean
|
||||
last_active_at: number | null
|
||||
profile_image_url: string
|
||||
public_metadata: Record<string, unknown>
|
||||
private_metadata: Record<string, unknown>
|
||||
unsafe_metadata: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ClerkEmailAddress {
|
||||
id: string
|
||||
object: 'email_address'
|
||||
email_address: string
|
||||
verification: ClerkVerification | null
|
||||
linked_to: ClerkLinkedIdentifier[]
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export interface ClerkPhoneNumber {
|
||||
id: string
|
||||
object: 'phone_number'
|
||||
phone_number: string
|
||||
reserved_for_second_factor: boolean
|
||||
default_second_factor: boolean
|
||||
verification: ClerkVerification | null
|
||||
linked_to: ClerkLinkedIdentifier[]
|
||||
backup_codes: string[] | null
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export interface ClerkWeb3Wallet {
|
||||
id: string
|
||||
object: 'web3_wallet'
|
||||
web3_wallet: string
|
||||
verification: ClerkVerification | null
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export interface ClerkExternalAccount {
|
||||
id: string
|
||||
object: 'external_account'
|
||||
provider: string
|
||||
identification_id: string
|
||||
provider_user_id: string
|
||||
approved_scopes: string
|
||||
email_address: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
image_url: string
|
||||
username: string | null
|
||||
public_metadata: Record<string, unknown>
|
||||
label: string | null
|
||||
verification: ClerkVerification | null
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export interface ClerkVerification {
|
||||
status: string
|
||||
strategy: string
|
||||
attempts: number | null
|
||||
expire_at: number | null
|
||||
}
|
||||
|
||||
export interface ClerkLinkedIdentifier {
|
||||
type: string
|
||||
id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Clerk Organization object
|
||||
*/
|
||||
export interface ClerkOrganization {
|
||||
id: string
|
||||
object: 'organization'
|
||||
name: string
|
||||
slug: string
|
||||
image_url: string
|
||||
has_image: boolean
|
||||
members_count?: number
|
||||
pending_invitations_count?: number
|
||||
max_allowed_memberships: number
|
||||
admin_delete_enabled: boolean
|
||||
public_metadata: Record<string, unknown>
|
||||
private_metadata: Record<string, unknown>
|
||||
created_by: string
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Clerk Session object
|
||||
*/
|
||||
export interface ClerkSession {
|
||||
id: string
|
||||
object: 'session'
|
||||
user_id: string
|
||||
client_id: string
|
||||
actor: Record<string, unknown> | null
|
||||
status:
|
||||
| 'abandoned'
|
||||
| 'active'
|
||||
| 'ended'
|
||||
| 'expired'
|
||||
| 'pending'
|
||||
| 'removed'
|
||||
| 'replaced'
|
||||
| 'revoked'
|
||||
last_active_organization_id: string | null
|
||||
last_active_at: number
|
||||
expire_at: number
|
||||
abandon_at: number
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed email address for outputs
|
||||
*/
|
||||
export interface ClerkEmailAddressOutput {
|
||||
id: string
|
||||
emailAddress: string
|
||||
verified?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed phone number for outputs
|
||||
*/
|
||||
export interface ClerkPhoneNumberOutput {
|
||||
id: string
|
||||
phoneNumber: string
|
||||
verified?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed user for list outputs
|
||||
*/
|
||||
export interface ClerkUserOutput {
|
||||
id: string
|
||||
username: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
imageUrl: string | null
|
||||
hasImage: boolean
|
||||
primaryEmailAddressId: string | null
|
||||
primaryPhoneNumberId: string | null
|
||||
emailAddresses: ClerkEmailAddressOutput[]
|
||||
phoneNumbers: ClerkPhoneNumberOutput[]
|
||||
externalId: string | null
|
||||
passwordEnabled: boolean
|
||||
twoFactorEnabled: boolean
|
||||
banned: boolean
|
||||
locked: boolean
|
||||
lastSignInAt: number | null
|
||||
lastActiveAt: number | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
publicMetadata: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed organization for outputs
|
||||
*/
|
||||
export interface ClerkOrganizationOutput {
|
||||
id: string
|
||||
name: string
|
||||
slug: string | null
|
||||
imageUrl: string | null
|
||||
hasImage: boolean
|
||||
membersCount: number | null
|
||||
pendingInvitationsCount: number | null
|
||||
maxAllowedMemberships: number
|
||||
adminDeleteEnabled: boolean
|
||||
createdBy: string | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
publicMetadata: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed session for outputs
|
||||
*/
|
||||
export interface ClerkSessionOutput {
|
||||
id: string
|
||||
userId: string
|
||||
clientId: string
|
||||
status: string
|
||||
lastActiveAt: number | null
|
||||
lastActiveOrganizationId: string | null
|
||||
expireAt: number | null
|
||||
abandonAt: number | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
// List Users
|
||||
export interface ClerkListUsersParams {
|
||||
secretKey: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
orderBy?: string
|
||||
emailAddress?: string
|
||||
phoneNumber?: string
|
||||
externalId?: string
|
||||
username?: string
|
||||
userId?: string
|
||||
query?: string
|
||||
}
|
||||
|
||||
export interface ClerkListUsersResponse extends ToolResponse {
|
||||
output: {
|
||||
users: ClerkUserOutput[]
|
||||
totalCount: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get User
|
||||
export interface ClerkGetUserParams {
|
||||
secretKey: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface ClerkGetUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
username: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
imageUrl: string | null
|
||||
hasImage: boolean
|
||||
primaryEmailAddressId: string | null
|
||||
primaryPhoneNumberId: string | null
|
||||
primaryWeb3WalletId: string | null
|
||||
emailAddresses: ClerkEmailAddressOutput[]
|
||||
phoneNumbers: ClerkPhoneNumberOutput[]
|
||||
externalId: string | null
|
||||
passwordEnabled: boolean
|
||||
twoFactorEnabled: boolean
|
||||
totpEnabled: boolean
|
||||
backupCodeEnabled: boolean
|
||||
banned: boolean
|
||||
locked: boolean
|
||||
deleteSelfEnabled: boolean
|
||||
createOrganizationEnabled: boolean
|
||||
lastSignInAt: number | null
|
||||
lastActiveAt: number | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
publicMetadata: Record<string, unknown>
|
||||
privateMetadata: Record<string, unknown>
|
||||
unsafeMetadata: Record<string, unknown>
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Create User
|
||||
export interface ClerkCreateUserParams {
|
||||
secretKey: string
|
||||
emailAddress?: string | string[]
|
||||
phoneNumber?: string | string[]
|
||||
username?: string
|
||||
password?: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
externalId?: string
|
||||
publicMetadata?: Record<string, unknown>
|
||||
privateMetadata?: Record<string, unknown>
|
||||
unsafeMetadata?: Record<string, unknown>
|
||||
skipPasswordChecks?: boolean
|
||||
skipPasswordRequirement?: boolean
|
||||
}
|
||||
|
||||
export interface ClerkCreateUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
username: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
imageUrl: string | null
|
||||
primaryEmailAddressId: string | null
|
||||
primaryPhoneNumberId: string | null
|
||||
emailAddresses: ClerkEmailAddressOutput[]
|
||||
phoneNumbers: ClerkPhoneNumberOutput[]
|
||||
externalId: string | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
publicMetadata: Record<string, unknown>
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Update User
|
||||
export interface ClerkUpdateUserParams {
|
||||
secretKey: string
|
||||
userId: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
username?: string
|
||||
password?: string
|
||||
externalId?: string
|
||||
primaryEmailAddressId?: string
|
||||
primaryPhoneNumberId?: string
|
||||
publicMetadata?: Record<string, unknown>
|
||||
privateMetadata?: Record<string, unknown>
|
||||
unsafeMetadata?: Record<string, unknown>
|
||||
skipPasswordChecks?: boolean
|
||||
}
|
||||
|
||||
export interface ClerkUpdateUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
username: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
imageUrl: string | null
|
||||
primaryEmailAddressId: string | null
|
||||
primaryPhoneNumberId: string | null
|
||||
emailAddresses: ClerkEmailAddressOutput[]
|
||||
phoneNumbers: ClerkPhoneNumberOutput[]
|
||||
externalId: string | null
|
||||
banned: boolean
|
||||
locked: boolean
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
publicMetadata: Record<string, unknown>
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Delete User
|
||||
export interface ClerkDeleteUserParams {
|
||||
secretKey: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface ClerkDeleteUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
object: string
|
||||
deleted: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Organizations
|
||||
export interface ClerkListOrganizationsParams {
|
||||
secretKey: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
includeMembersCount?: boolean
|
||||
query?: string
|
||||
orderBy?: string
|
||||
}
|
||||
|
||||
export interface ClerkListOrganizationsResponse extends ToolResponse {
|
||||
output: {
|
||||
organizations: ClerkOrganizationOutput[]
|
||||
totalCount: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get Organization
|
||||
export interface ClerkGetOrganizationParams {
|
||||
secretKey: string
|
||||
organizationId: string
|
||||
}
|
||||
|
||||
export interface ClerkGetOrganizationResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
name: string
|
||||
slug: string | null
|
||||
imageUrl: string | null
|
||||
hasImage: boolean
|
||||
membersCount: number | null
|
||||
pendingInvitationsCount: number | null
|
||||
maxAllowedMemberships: number
|
||||
adminDeleteEnabled: boolean
|
||||
createdBy: string | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
publicMetadata: Record<string, unknown>
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Create Organization
|
||||
export interface ClerkCreateOrganizationParams {
|
||||
secretKey: string
|
||||
name: string
|
||||
createdBy: string
|
||||
slug?: string
|
||||
maxAllowedMemberships?: number
|
||||
publicMetadata?: Record<string, unknown>
|
||||
privateMetadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ClerkCreateOrganizationResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
name: string
|
||||
slug: string | null
|
||||
imageUrl: string | null
|
||||
hasImage: boolean
|
||||
membersCount: number | null
|
||||
pendingInvitationsCount: number | null
|
||||
maxAllowedMemberships: number
|
||||
adminDeleteEnabled: boolean
|
||||
createdBy: string | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
publicMetadata: Record<string, unknown>
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Sessions
|
||||
export interface ClerkListSessionsParams {
|
||||
secretKey: string
|
||||
userId?: string
|
||||
clientId?: string
|
||||
status?:
|
||||
| 'abandoned'
|
||||
| 'active'
|
||||
| 'ended'
|
||||
| 'expired'
|
||||
| 'pending'
|
||||
| 'removed'
|
||||
| 'replaced'
|
||||
| 'revoked'
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface ClerkListSessionsResponse extends ToolResponse {
|
||||
output: {
|
||||
sessions: ClerkSessionOutput[]
|
||||
totalCount: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get Session
|
||||
export interface ClerkGetSessionParams {
|
||||
secretKey: string
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface ClerkGetSessionResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
userId: string
|
||||
clientId: string
|
||||
status: string
|
||||
lastActiveAt: number | null
|
||||
lastActiveOrganizationId: string | null
|
||||
expireAt: number | null
|
||||
abandonAt: number | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke Session
|
||||
export interface ClerkRevokeSessionParams {
|
||||
secretKey: string
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface ClerkRevokeSessionResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
userId: string
|
||||
clientId: string
|
||||
status: string
|
||||
lastActiveAt: number | null
|
||||
lastActiveOrganizationId: string | null
|
||||
expireAt: number | null
|
||||
abandonAt: number | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Generic response type for the block
|
||||
export type ClerkResponse =
|
||||
| ClerkListUsersResponse
|
||||
| ClerkGetUserResponse
|
||||
| ClerkCreateUserResponse
|
||||
| ClerkUpdateUserResponse
|
||||
| ClerkDeleteUserResponse
|
||||
| ClerkListOrganizationsResponse
|
||||
| ClerkGetOrganizationResponse
|
||||
| ClerkCreateOrganizationResponse
|
||||
| ClerkListSessionsResponse
|
||||
| ClerkGetSessionResponse
|
||||
| ClerkRevokeSessionResponse
|
||||
225
apps/sim/tools/clerk/update_user.ts
Normal file
225
apps/sim/tools/clerk/update_user.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkEmailAddress,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUpdateUserParams,
|
||||
ClerkUpdateUserResponse,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkUpdateUser')
|
||||
|
||||
export const clerkUpdateUserTool: ToolConfig<ClerkUpdateUserParams, ClerkUpdateUserResponse> = {
|
||||
id: 'clerk_update_user',
|
||||
name: 'Update User in Clerk',
|
||||
description: 'Update an existing user in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to update',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'First name of the user',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last name of the user',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Username (must be unique)',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New password (minimum 8 characters)',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'External system identifier',
|
||||
},
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of verified email to set as primary',
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of verified phone to set as primary',
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public metadata (JSON object)',
|
||||
},
|
||||
privateMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Private metadata (JSON object)',
|
||||
},
|
||||
unsafeMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unsafe metadata (JSON object)',
|
||||
},
|
||||
skipPasswordChecks: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip password validation checks',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
|
||||
if (params.firstName !== undefined) body.first_name = params.firstName?.trim()
|
||||
if (params.lastName !== undefined) body.last_name = params.lastName?.trim()
|
||||
if (params.username !== undefined) body.username = params.username?.trim()
|
||||
if (params.password !== undefined) body.password = params.password
|
||||
if (params.externalId !== undefined) body.external_id = params.externalId?.trim()
|
||||
if (params.primaryEmailAddressId !== undefined)
|
||||
body.primary_email_address_id = params.primaryEmailAddressId?.trim()
|
||||
if (params.primaryPhoneNumberId !== undefined)
|
||||
body.primary_phone_number_id = params.primaryPhoneNumberId?.trim()
|
||||
if (params.publicMetadata !== undefined) body.public_metadata = params.publicMetadata
|
||||
if (params.privateMetadata !== undefined) body.private_metadata = params.privateMetadata
|
||||
if (params.unsafeMetadata !== undefined) body.unsafe_metadata = params.unsafeMetadata
|
||||
if (params.skipPasswordChecks !== undefined)
|
||||
body.skip_password_checks = params.skipPasswordChecks
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to update user in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
verified: email.verification?.status === 'verified',
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
verified: phone.verification?.status === 'verified',
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Updated user ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
verified: { type: 'boolean', description: 'Whether email is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
verified: { type: 'boolean', description: 'Whether phone is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether user is locked' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -75,6 +75,19 @@ import {
|
||||
calendlyListWebhooksTool,
|
||||
} from '@/tools/calendly'
|
||||
import { clayPopulateTool } from '@/tools/clay'
|
||||
import {
|
||||
clerkCreateOrganizationTool,
|
||||
clerkCreateUserTool,
|
||||
clerkDeleteUserTool,
|
||||
clerkGetOrganizationTool,
|
||||
clerkGetSessionTool,
|
||||
clerkGetUserTool,
|
||||
clerkListOrganizationsTool,
|
||||
clerkListSessionsTool,
|
||||
clerkListUsersTool,
|
||||
clerkRevokeSessionTool,
|
||||
clerkUpdateUserTool,
|
||||
} from '@/tools/clerk'
|
||||
import {
|
||||
confluenceCreateCommentTool,
|
||||
confluenceCreatePageTool,
|
||||
@@ -2571,6 +2584,17 @@ export const tools: Record<string, ToolConfig> = {
|
||||
telegram_send_video: telegramSendVideoTool,
|
||||
telegram_send_document: telegramSendDocumentTool,
|
||||
clay_populate: clayPopulateTool,
|
||||
clerk_list_users: clerkListUsersTool,
|
||||
clerk_get_user: clerkGetUserTool,
|
||||
clerk_create_user: clerkCreateUserTool,
|
||||
clerk_update_user: clerkUpdateUserTool,
|
||||
clerk_delete_user: clerkDeleteUserTool,
|
||||
clerk_list_organizations: clerkListOrganizationsTool,
|
||||
clerk_get_organization: clerkGetOrganizationTool,
|
||||
clerk_create_organization: clerkCreateOrganizationTool,
|
||||
clerk_list_sessions: clerkListSessionsTool,
|
||||
clerk_get_session: clerkGetSessionTool,
|
||||
clerk_revoke_session: clerkRevokeSessionTool,
|
||||
discord_send_message: discordSendMessageTool,
|
||||
discord_get_messages: discordGetMessagesTool,
|
||||
discord_get_server: discordGetServerTool,
|
||||
|
||||
Reference in New Issue
Block a user