chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,123 @@
import { createLogger } from '@sim/logger'
import type {
ClerkAddOrganizationMemberParams,
ClerkAddOrganizationMemberResponse,
ClerkApiError,
ClerkOrganizationMembership,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkAddOrganizationMember')
export const clerkAddOrganizationMemberTool: ToolConfig<
ClerkAddOrganizationMemberParams,
ClerkAddOrganizationMemberResponse
> = {
id: 'clerk_add_organization_member',
name: 'Add Organization Member in Clerk',
description: 'Add a user as a member of a Clerk organization with a given role',
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 of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the user to add as a member',
},
role: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Role to assign, e.g. org:admin or org:member',
},
},
request: {
url: (params) =>
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships`,
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) => ({
user_id: params.userId?.trim(),
role: params.role,
}),
},
transformResponse: async (response: Response) => {
const data: ClerkOrganizationMembership | 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 add organization member in Clerk'
)
}
const membership = data as ClerkOrganizationMembership
return {
success: true,
output: {
id: membership.id,
role: membership.role,
roleName: membership.role_name ?? null,
permissions: membership.permissions ?? [],
organizationId: membership.organization.id,
userId: membership.public_user_data.user_id,
firstName: membership.public_user_data.first_name ?? null,
lastName: membership.public_user_data.last_name ?? null,
imageUrl: membership.public_user_data.image_url ?? null,
identifier: membership.public_user_data.identifier ?? null,
username: membership.public_user_data.username ?? null,
banned: membership.public_user_data.banned ?? false,
publicMetadata: membership.public_metadata ?? {},
createdAt: membership.created_at,
updatedAt: membership.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Membership ID' },
role: { type: 'string', description: 'Member role' },
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
permissions: {
type: 'array',
description: 'Permissions granted by the role',
items: { type: 'string' },
},
organizationId: { type: 'string', description: 'Organization ID' },
userId: { type: 'string', description: 'Member user ID' },
firstName: { type: 'string', description: 'Member first name', optional: true },
lastName: { type: 'string', description: 'Member last name', optional: true },
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
identifier: { type: 'string', description: 'Member identifier (e.g., email)', optional: true },
username: { type: 'string', description: 'Member username', optional: true },
banned: { type: 'boolean', description: 'Whether the member is banned' },
publicMetadata: { type: 'json', description: 'Public metadata' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+87
View File
@@ -0,0 +1,87 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkBanUserParams,
ClerkBanUserResponse,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkBanUser')
export const clerkBanUserTool: ToolConfig<ClerkBanUserParams, ClerkBanUserResponse> = {
id: 'clerk_ban_user',
name: 'Ban User in Clerk',
description: 'Ban a user, preventing them from signing in',
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 ban (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/ban`,
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: 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 ban 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,
banned: user.banned ?? false,
locked: user.locked ?? false,
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
updatedAt: user.updated_at,
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 },
banned: { type: 'boolean', description: 'Whether the user is banned' },
locked: { type: 'boolean', description: 'Whether the user is locked' },
lockoutExpiresInSeconds: {
type: 'number',
description: 'Seconds until lockout expires',
optional: true,
},
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+121
View File
@@ -0,0 +1,121 @@
import { createLogger } from '@sim/logger'
import type {
ClerkActorToken,
ClerkApiError,
ClerkCreateActorTokenParams,
ClerkCreateActorTokenResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkCreateActorToken')
export const clerkCreateActorTokenTool: ToolConfig<
ClerkCreateActorTokenParams,
ClerkCreateActorTokenResponse
> = {
id: 'clerk_create_actor_token',
name: 'Create Actor Token in Clerk',
description:
'Create an actor token to impersonate a user (God Mode / act-as-user), e.g. for support tooling',
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: 'ID of the user to impersonate',
},
actor: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Actor JSON object identifying who is impersonating, must include a "sub" field, e.g. {"sub": "user_support_agent_id"}',
},
expiresInSeconds: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Seconds until the token expires (default 3600)',
},
sessionMaxDurationInSeconds: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Max duration in seconds for sessions created with this token (default 1800)',
},
},
request: {
url: () => 'https://api.clerk.com/v1/actor_tokens',
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> = {
user_id: params.userId?.trim(),
actor: params.actor,
}
if (params.expiresInSeconds !== undefined) body.expires_in_seconds = params.expiresInSeconds
if (params.sessionMaxDurationInSeconds !== undefined)
body.session_max_duration_in_seconds = params.sessionMaxDurationInSeconds
return body
},
},
transformResponse: async (response: Response) => {
const data: ClerkActorToken | 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 actor token in Clerk'
)
}
const actorToken = data as ClerkActorToken
return {
success: true,
output: {
id: actorToken.id,
status: actorToken.status,
userId: actorToken.user_id,
actor: actorToken.actor ?? {},
token: actorToken.token ?? null,
url: actorToken.url ?? null,
createdAt: actorToken.created_at,
updatedAt: actorToken.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Actor token ID' },
status: { type: 'string', description: 'Actor token status' },
userId: { type: 'string', description: 'ID of the impersonated user' },
actor: { type: 'json', description: 'Actor object identifying who is impersonating' },
token: { type: 'string', description: 'Signed actor token (JWT)', optional: true },
url: { type: 'string', description: 'Sign-in URL for the actor token', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,101 @@
import { createLogger } from '@sim/logger'
import type {
ClerkAllowlistIdentifier,
ClerkApiError,
ClerkCreateAllowlistIdentifierParams,
ClerkCreateAllowlistIdentifierResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkCreateAllowlistIdentifier')
export const clerkCreateAllowlistIdentifierTool: ToolConfig<
ClerkCreateAllowlistIdentifierParams,
ClerkCreateAllowlistIdentifierResponse
> = {
id: 'clerk_create_allowlist_identifier',
name: 'Create Allowlist Identifier in Clerk',
description: 'Add an email, phone number, or web3 wallet to your Clerk instance allowlist',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
identifier: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Email address, phone number, or web3 wallet to allow (wildcards like *@example.com supported for email)',
},
notify: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to notify the identifier owner by email (default false)',
},
},
request: {
url: () => 'https://api.clerk.com/v1/allowlist_identifiers',
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> = {
identifier: params.identifier?.trim(),
}
if (params.notify !== undefined) body.notify = params.notify
return body
},
},
transformResponse: async (response: Response) => {
const data: ClerkAllowlistIdentifier | 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 allowlist identifier in Clerk'
)
}
const identifier = data as ClerkAllowlistIdentifier
return {
success: true,
output: {
id: identifier.id,
identifier: identifier.identifier,
identifierType: identifier.identifier_type,
invitationId: identifier.invitation_id ?? null,
createdAt: identifier.created_at,
updatedAt: identifier.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Allowlist identifier ID' },
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
identifierType: { type: 'string', description: 'Type of identifier' },
invitationId: { type: 'string', description: 'Associated invitation ID', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,87 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkBlocklistIdentifier,
ClerkCreateBlocklistIdentifierParams,
ClerkCreateBlocklistIdentifierResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkCreateBlocklistIdentifier')
export const clerkCreateBlocklistIdentifierTool: ToolConfig<
ClerkCreateBlocklistIdentifierParams,
ClerkCreateBlocklistIdentifierResponse
> = {
id: 'clerk_create_blocklist_identifier',
name: 'Create Blocklist Identifier in Clerk',
description:
'Add an email, phone number, or web3 wallet to your Clerk instance blocklist to prevent sign-ups',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
identifier: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email address, phone number, or web3 wallet to block',
},
},
request: {
url: () => 'https://api.clerk.com/v1/blocklist_identifiers',
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) => ({
identifier: params.identifier?.trim(),
}),
},
transformResponse: async (response: Response) => {
const data: ClerkBlocklistIdentifier | 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 blocklist identifier in Clerk'
)
}
const identifier = data as ClerkBlocklistIdentifier
return {
success: true,
output: {
id: identifier.id,
identifier: identifier.identifier,
identifierType: identifier.identifier_type,
createdAt: identifier.created_at,
updatedAt: identifier.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Blocklist identifier ID' },
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
identifierType: { type: 'string', description: 'Type of identifier' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+147
View File
@@ -0,0 +1,147 @@
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 who will become admin (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
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' },
},
}
@@ -0,0 +1,167 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkCreateOrganizationInvitationParams,
ClerkCreateOrganizationInvitationResponse,
ClerkOrganizationInvitation,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkCreateOrganizationInvitation')
export const clerkCreateOrganizationInvitationTool: ToolConfig<
ClerkCreateOrganizationInvitationParams,
ClerkCreateOrganizationInvitationResponse
> = {
id: 'clerk_create_organization_invitation',
name: 'Create Organization Invitation in Clerk',
description: 'Invite a user by email to join a Clerk organization with a given role',
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 of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
emailAddress: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email address of the user to invite',
},
role: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Role to assign on acceptance, e.g. org:admin or org:member',
},
inviterUserId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID of the inviter',
},
redirectUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'URL to redirect to after the invitation is accepted',
},
expiresInDays: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Days until the invitation expires (1-365, default 30)',
},
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)',
},
notify: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether Clerk sends the invitation email (default true)',
},
},
request: {
url: (params) =>
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/invitations`,
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> = {
email_address: params.emailAddress,
role: params.role,
}
if (params.inviterUserId !== undefined) body.inviter_user_id = params.inviterUserId
if (params.redirectUrl !== undefined) body.redirect_url = params.redirectUrl
if (params.expiresInDays !== undefined) body.expires_in_days = params.expiresInDays
if (params.publicMetadata !== undefined) body.public_metadata = params.publicMetadata
if (params.privateMetadata !== undefined) body.private_metadata = params.privateMetadata
if (params.notify !== undefined) body.notify = params.notify
return body
},
},
transformResponse: async (response: Response) => {
const data: ClerkOrganizationInvitation | 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 invitation in Clerk'
)
}
const invitation = data as ClerkOrganizationInvitation
return {
success: true,
output: {
id: invitation.id,
emailAddress: invitation.email_address,
role: invitation.role,
roleName: invitation.role_name ?? null,
organizationId: invitation.organization_id,
inviterId: invitation.inviter_id ?? null,
inviterEmail: invitation.public_inviter_data?.identifier ?? null,
inviterFirstName: invitation.public_inviter_data?.first_name ?? null,
inviterLastName: invitation.public_inviter_data?.last_name ?? null,
status: invitation.status,
url: invitation.url ?? null,
expiresAt: invitation.expires_at ?? null,
publicMetadata: invitation.public_metadata ?? {},
createdAt: invitation.created_at,
updatedAt: invitation.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Invitation ID' },
emailAddress: { type: 'string', description: 'Invited email address' },
role: { type: 'string', description: 'Role to assign on acceptance' },
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
organizationId: { type: 'string', description: 'Organization ID' },
inviterId: { type: 'string', description: 'User ID of the inviter', optional: true },
inviterEmail: { type: 'string', description: "Inviter's email address", optional: true },
inviterFirstName: { type: 'string', description: "Inviter's first name", optional: true },
inviterLastName: { type: 'string', description: "Inviter's last name", optional: true },
status: { type: 'string', description: 'Invitation status' },
url: { type: 'string', description: 'Invitation URL', optional: true },
expiresAt: { type: 'number', description: 'Expiration timestamp', optional: true },
publicMetadata: { type: 'json', description: 'Public metadata' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+227
View 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' },
},
}
@@ -0,0 +1,80 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkDeleteAllowlistIdentifierParams,
ClerkDeleteAllowlistIdentifierResponse,
ClerkDeleteResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkDeleteAllowlistIdentifier')
export const clerkDeleteAllowlistIdentifierTool: ToolConfig<
ClerkDeleteAllowlistIdentifierParams,
ClerkDeleteAllowlistIdentifierResponse
> = {
id: 'clerk_delete_allowlist_identifier',
name: 'Delete Allowlist Identifier from Clerk',
description: 'Remove an identifier from your Clerk instance allowlist',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
identifierId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the allowlist identifier to delete',
},
},
request: {
url: (params) =>
`https://api.clerk.com/v1/allowlist_identifiers/${params.identifierId?.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 allowlist identifier from Clerk'
)
}
const deleteData = data as ClerkDeleteResponse
return {
success: true,
output: {
id: deleteData.id,
object: deleteData.object ?? 'allowlist_identifier',
deleted: deleteData.deleted ?? true,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Deleted allowlist identifier ID' },
object: { type: 'string', description: 'Object type (allowlist_identifier)' },
deleted: { type: 'boolean', description: 'Whether the identifier was deleted' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,80 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkDeleteBlocklistIdentifierParams,
ClerkDeleteBlocklistIdentifierResponse,
ClerkDeleteResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkDeleteBlocklistIdentifier')
export const clerkDeleteBlocklistIdentifierTool: ToolConfig<
ClerkDeleteBlocklistIdentifierParams,
ClerkDeleteBlocklistIdentifierResponse
> = {
id: 'clerk_delete_blocklist_identifier',
name: 'Delete Blocklist Identifier from Clerk',
description: 'Remove an identifier from your Clerk instance blocklist',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
identifierId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the blocklist identifier to delete',
},
},
request: {
url: (params) =>
`https://api.clerk.com/v1/blocklist_identifiers/${params.identifierId?.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 blocklist identifier from Clerk'
)
}
const deleteData = data as ClerkDeleteResponse
return {
success: true,
output: {
id: deleteData.id,
object: deleteData.object ?? 'blocklist_identifier',
deleted: deleteData.deleted ?? true,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Deleted blocklist identifier ID' },
object: { type: 'string', description: 'Object type (blocklist_identifier)' },
deleted: { type: 'boolean', description: 'Whether the identifier was deleted' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,78 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkDeleteOrganizationParams,
ClerkDeleteOrganizationResponse,
ClerkDeleteResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkDeleteOrganization')
export const clerkDeleteOrganizationTool: ToolConfig<
ClerkDeleteOrganizationParams,
ClerkDeleteOrganizationResponse
> = {
id: 'clerk_delete_organization',
name: 'Delete Organization from Clerk',
description: 'Delete an organization 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',
},
organizationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the organization to delete (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId?.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 organization from Clerk'
)
}
const deleteData = data as ClerkDeleteResponse
return {
success: true,
output: {
id: deleteData.id,
object: deleteData.object ?? 'organization',
deleted: deleteData.deleted ?? true,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Deleted organization ID' },
object: { type: 'string', description: 'Object type (organization)' },
deleted: { type: 'boolean', description: 'Whether the organization was deleted' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+75
View 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 (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
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' },
},
}
+94
View File
@@ -0,0 +1,94 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkGetJwtTemplateParams,
ClerkGetJwtTemplateResponse,
ClerkJwtTemplate,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkGetJwtTemplate')
export const clerkGetJwtTemplateTool: ToolConfig<
ClerkGetJwtTemplateParams,
ClerkGetJwtTemplateResponse
> = {
id: 'clerk_get_jwt_template',
name: 'Get JWT Template from Clerk',
description: 'Retrieve a single custom JWT template 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',
},
templateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the JWT template to retrieve',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/jwt_templates/${params.templateId?.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: ClerkJwtTemplate | 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 JWT template from Clerk'
)
}
const template = data as ClerkJwtTemplate
return {
success: true,
output: {
id: template.id,
name: template.name,
claims: template.claims ?? {},
lifetime: template.lifetime,
allowedClockSkew: template.allowed_clock_skew,
customSigningKey: template.custom_signing_key ?? false,
signingAlgorithm: template.signing_algorithm,
createdAt: template.created_at,
updatedAt: template.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'JWT template ID' },
name: { type: 'string', description: 'JWT template name' },
claims: { type: 'json', description: 'Custom claims defined on the template' },
lifetime: { type: 'number', description: 'Token lifetime in seconds' },
allowedClockSkew: { type: 'number', description: 'Allowed clock skew in seconds' },
customSigningKey: {
type: 'boolean',
description: 'Whether a custom signing key is configured',
},
signingAlgorithm: { type: 'string', description: 'Signing algorithm used' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+103
View File
@@ -0,0 +1,103 @@
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 (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC or my-org-slug)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId?.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: 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
View 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 (e.g., sess_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId?.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: 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
View 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 (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
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' },
},
}
@@ -0,0 +1,116 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkGetUserOauthTokenParams,
ClerkGetUserOauthTokenResponse,
ClerkOAuthAccessToken,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkGetUserOauthToken')
export const clerkGetUserOauthTokenTool: ToolConfig<
ClerkGetUserOauthTokenParams,
ClerkGetUserOauthTokenResponse
> = {
id: 'clerk_get_user_oauth_token',
name: 'Get User OAuth Access Token from Clerk',
description:
"Retrieve a user's OAuth access token for a connected external provider (e.g. Google, GitHub, Microsoft) obtained via Clerk SSO",
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 (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
provider: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'OAuth provider slug, e.g. google, github, microsoft, discord (without the oauth_ prefix)',
},
},
request: {
url: (params) => {
const providerSlug = params.provider?.trim().replace(/^oauth_/, '')
return `https://api.clerk.com/v1/users/${params.userId?.trim()}/oauth_access_tokens/oauth_${providerSlug}`
},
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: ClerkOAuthAccessToken[] | 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 OAuth access token from Clerk'
)
}
const tokens = data as ClerkOAuthAccessToken[]
return {
success: true,
output: {
accessTokens: tokens.map((token) => ({
externalAccountId: token.external_account_id,
token: token.token,
expiresAt: token.expires_at ?? null,
provider: token.provider,
label: token.label ?? null,
scopes: token.scopes ?? [],
publicMetadata: token.public_metadata ?? {},
})),
success: true,
},
}
},
outputs: {
accessTokens: {
type: 'array',
description: 'OAuth access tokens for the connected provider',
items: {
type: 'object',
properties: {
externalAccountId: { type: 'string', description: 'External account ID' },
token: { type: 'string', description: 'OAuth access token' },
expiresAt: { type: 'number', description: 'Expiration timestamp', optional: true },
provider: { type: 'string', description: 'OAuth provider slug' },
label: { type: 'string', description: 'Token label', optional: true },
scopes: {
type: 'array',
description: 'OAuth scopes granted to the token',
items: { type: 'string' },
},
publicMetadata: {
type: 'json',
description: 'Public metadata associated with the token',
},
},
},
},
success: { type: 'boolean', description: 'Operation success status' },
},
}
+34
View File
@@ -0,0 +1,34 @@
export { clerkAddOrganizationMemberTool } from './add_organization_member'
export { clerkBanUserTool } from './ban_user'
export { clerkCreateActorTokenTool } from './create_actor_token'
export { clerkCreateAllowlistIdentifierTool } from './create_allowlist_identifier'
export { clerkCreateBlocklistIdentifierTool } from './create_blocklist_identifier'
export { clerkCreateOrganizationTool } from './create_organization'
export { clerkCreateOrganizationInvitationTool } from './create_organization_invitation'
export { clerkCreateUserTool } from './create_user'
export { clerkDeleteAllowlistIdentifierTool } from './delete_allowlist_identifier'
export { clerkDeleteBlocklistIdentifierTool } from './delete_blocklist_identifier'
export { clerkDeleteOrganizationTool } from './delete_organization'
export { clerkDeleteUserTool } from './delete_user'
export { clerkGetJwtTemplateTool } from './get_jwt_template'
export { clerkGetOrganizationTool } from './get_organization'
export { clerkGetSessionTool } from './get_session'
export { clerkGetUserTool } from './get_user'
export { clerkGetUserOauthTokenTool } from './get_user_oauth_token'
export { clerkListAllowlistIdentifiersTool } from './list_allowlist_identifiers'
export { clerkListBlocklistIdentifiersTool } from './list_blocklist_identifiers'
export { clerkListJwtTemplatesTool } from './list_jwt_templates'
export { clerkListOrganizationInvitationsTool } from './list_organization_invitations'
export { clerkListOrganizationMembershipsTool } from './list_organization_memberships'
export { clerkListOrganizationsTool } from './list_organizations'
export { clerkListSessionsTool } from './list_sessions'
export { clerkListUsersTool } from './list_users'
export { clerkLockUserTool } from './lock_user'
export { clerkRemoveOrganizationMemberTool } from './remove_organization_member'
export { clerkRevokeActorTokenTool } from './revoke_actor_token'
export { clerkRevokeSessionTool } from './revoke_session'
export { clerkUnbanUserTool } from './unban_user'
export { clerkUnlockUserTool } from './unlock_user'
export { clerkUpdateOrganizationTool } from './update_organization'
export { clerkUpdateOrganizationMembershipTool } from './update_organization_membership'
export { clerkUpdateUserTool } from './update_user'
@@ -0,0 +1,119 @@
import { createLogger } from '@sim/logger'
import type {
ClerkAllowlistIdentifier,
ClerkApiError,
ClerkListAllowlistIdentifiersParams,
ClerkListAllowlistIdentifiersResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListAllowlistIdentifiers')
export const clerkListAllowlistIdentifiersTool: ToolConfig<
ClerkListAllowlistIdentifiersParams,
ClerkListAllowlistIdentifiersResponse
> = {
id: 'clerk_list_allowlist_identifiers',
name: 'List Allowlist Identifiers from Clerk',
description: 'List email/phone/web3-wallet identifiers on your Clerk instance allowlist',
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 (e.g., 10, 50, 100; range: 1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
},
},
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())
const queryString = queryParams.toString()
return queryString
? `https://api.clerk.com/v1/allowlist_identifiers?${queryString}`
: 'https://api.clerk.com/v1/allowlist_identifiers'
},
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: ClerkAllowlistIdentifier[] | 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 allowlist identifiers from Clerk'
)
}
const identifiers = (data as ClerkAllowlistIdentifier[]).map((identifier) => ({
id: identifier.id,
identifier: identifier.identifier,
identifierType: identifier.identifier_type,
invitationId: identifier.invitation_id ?? null,
createdAt: identifier.created_at,
updatedAt: identifier.updated_at,
}))
return {
success: true,
output: {
identifiers,
totalCount: identifiers.length,
success: true,
},
}
},
outputs: {
identifiers: {
type: 'array',
description: 'Array of Clerk allowlist identifier objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Allowlist identifier ID' },
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
identifierType: { type: 'string', description: 'Type of identifier' },
invitationId: {
type: 'string',
description: 'Associated invitation ID',
optional: true,
},
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
},
},
},
totalCount: { type: 'number', description: 'Total number of allowlist identifiers' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,94 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkBlocklistIdentifier,
ClerkListBlocklistIdentifiersParams,
ClerkListBlocklistIdentifiersResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListBlocklistIdentifiers')
export const clerkListBlocklistIdentifiersTool: ToolConfig<
ClerkListBlocklistIdentifiersParams,
ClerkListBlocklistIdentifiersResponse
> = {
id: 'clerk_list_blocklist_identifiers',
name: 'List Blocklist Identifiers from Clerk',
description: 'List email/phone/web3-wallet identifiers on your Clerk instance blocklist',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
},
request: {
url: () => 'https://api.clerk.com/v1/blocklist_identifiers',
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: ClerkBlocklistIdentifier[]; 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 blocklist identifiers from Clerk'
)
}
const responseData = json as { data: ClerkBlocklistIdentifier[]; total_count: number }
const identifiers = responseData.data.map((identifier) => ({
id: identifier.id,
identifier: identifier.identifier,
identifierType: identifier.identifier_type,
createdAt: identifier.created_at,
updatedAt: identifier.updated_at,
}))
return {
success: true,
output: {
identifiers,
totalCount: responseData.total_count ?? identifiers.length,
success: true,
},
}
},
outputs: {
identifiers: {
type: 'array',
description: 'Array of Clerk blocklist identifier objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Blocklist identifier ID' },
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
identifierType: { type: 'string', description: 'Type of identifier' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
},
},
},
totalCount: { type: 'number', description: 'Total number of blocklist identifiers' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+101
View File
@@ -0,0 +1,101 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkJwtTemplate,
ClerkListJwtTemplatesParams,
ClerkListJwtTemplatesResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListJwtTemplates')
export const clerkListJwtTemplatesTool: ToolConfig<
ClerkListJwtTemplatesParams,
ClerkListJwtTemplatesResponse
> = {
id: 'clerk_list_jwt_templates',
name: 'List JWT Templates from Clerk',
description: 'List custom JWT templates configured on your Clerk instance',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
},
request: {
url: () => 'https://api.clerk.com/v1/jwt_templates',
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: ClerkJwtTemplate[] | 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 JWT templates from Clerk'
)
}
const templates = (data as ClerkJwtTemplate[]).map((template) => ({
id: template.id,
name: template.name,
claims: template.claims ?? {},
lifetime: template.lifetime,
allowedClockSkew: template.allowed_clock_skew,
customSigningKey: template.custom_signing_key ?? false,
signingAlgorithm: template.signing_algorithm,
createdAt: template.created_at,
updatedAt: template.updated_at,
}))
return {
success: true,
output: {
templates,
totalCount: templates.length,
success: true,
},
}
},
outputs: {
templates: {
type: 'array',
description: 'Array of Clerk JWT template objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'JWT template ID' },
name: { type: 'string', description: 'JWT template name' },
claims: { type: 'json', description: 'Custom claims defined on the template' },
lifetime: { type: 'number', description: 'Token lifetime in seconds' },
allowedClockSkew: { type: 'number', description: 'Allowed clock skew in seconds' },
customSigningKey: {
type: 'boolean',
description: 'Whether a custom signing key is configured',
},
signingAlgorithm: { type: 'string', description: 'Signing algorithm used' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
},
},
},
totalCount: { type: 'number', description: 'Total number of JWT templates' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,162 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkListOrganizationInvitationsParams,
ClerkListOrganizationInvitationsResponse,
ClerkOrganizationInvitation,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListOrganizationInvitations')
export const clerkListOrganizationInvitationsTool: ToolConfig<
ClerkListOrganizationInvitationsParams,
ClerkListOrganizationInvitationsResponse
> = {
id: 'clerk_list_organization_invitations',
name: 'List Organization Invitations from Clerk',
description: 'List pending and past invitations for a Clerk organization',
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 of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by status: pending, accepted, revoked, or expired',
},
emailAddress: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by invited email address',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field (created_at, email_address) with +/- prefix (default: -created_at)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.status) queryParams.append('status', params.status)
if (params.emailAddress) queryParams.append('email_address', params.emailAddress)
if (params.orderBy) queryParams.append('order_by', params.orderBy)
if (params.limit) queryParams.append('limit', params.limit.toString())
if (params.offset) queryParams.append('offset', params.offset.toString())
const queryString = queryParams.toString()
const base = `https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/invitations`
return queryString ? `${base}?${queryString}` : base
},
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: ClerkOrganizationInvitation[]; 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 organization invitations from Clerk'
)
}
const responseData = json as { data: ClerkOrganizationInvitation[]; total_count: number }
const invitations = responseData.data.map((invitation) => ({
id: invitation.id,
emailAddress: invitation.email_address,
role: invitation.role,
roleName: invitation.role_name ?? null,
organizationId: invitation.organization_id,
inviterId: invitation.inviter_id ?? null,
inviterEmail: invitation.public_inviter_data?.identifier ?? null,
inviterFirstName: invitation.public_inviter_data?.first_name ?? null,
inviterLastName: invitation.public_inviter_data?.last_name ?? null,
status: invitation.status,
url: invitation.url ?? null,
expiresAt: invitation.expires_at ?? null,
publicMetadata: invitation.public_metadata ?? {},
createdAt: invitation.created_at,
updatedAt: invitation.updated_at,
}))
return {
success: true,
output: {
invitations,
totalCount: responseData.total_count ?? invitations.length,
success: true,
},
}
},
outputs: {
invitations: {
type: 'array',
description: 'Array of Clerk organization invitation objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Invitation ID' },
emailAddress: { type: 'string', description: 'Invited email address' },
role: { type: 'string', description: 'Role to assign on acceptance' },
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
organizationId: { type: 'string', description: 'Organization ID' },
inviterId: { type: 'string', description: 'User ID of the inviter', optional: true },
inviterEmail: { type: 'string', description: "Inviter's email address", optional: true },
inviterFirstName: { type: 'string', description: "Inviter's first name", optional: true },
inviterLastName: { type: 'string', description: "Inviter's last name", optional: true },
status: { type: 'string', description: 'Invitation status' },
url: { type: 'string', description: 'Invitation URL', optional: true },
expiresAt: { type: 'number', description: 'Expiration timestamp', optional: true },
publicMetadata: { type: 'json', description: 'Public metadata' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
},
},
},
totalCount: { type: 'number', description: 'Total number of invitations' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,167 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkListOrganizationMembershipsParams,
ClerkListOrganizationMembershipsResponse,
ClerkOrganizationMembership,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListOrganizationMemberships')
export const clerkListOrganizationMembershipsTool: ToolConfig<
ClerkListOrganizationMembershipsParams,
ClerkListOrganizationMembershipsResponse
> = {
id: 'clerk_list_organization_memberships',
name: 'List Organization Memberships from Clerk',
description: 'List members of a Clerk organization 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',
},
organizationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field (e.g., created_at) with +/- prefix for direction',
},
role: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by role, comma-separated for multiple (e.g., org:admin,org:member)',
},
},
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.role) {
params.role.split(',').forEach((role) => {
queryParams.append('role', role.trim())
})
}
const queryString = queryParams.toString()
const base = `https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships`
return queryString ? `${base}?${queryString}` : base
},
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: ClerkOrganizationMembership[]; 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 organization memberships from Clerk'
)
}
const responseData = json as { data: ClerkOrganizationMembership[]; total_count: number }
const memberships = responseData.data.map((membership) => ({
id: membership.id,
role: membership.role,
roleName: membership.role_name ?? null,
permissions: membership.permissions ?? [],
organizationId: membership.organization.id,
userId: membership.public_user_data.user_id,
firstName: membership.public_user_data.first_name ?? null,
lastName: membership.public_user_data.last_name ?? null,
imageUrl: membership.public_user_data.image_url ?? null,
identifier: membership.public_user_data.identifier ?? null,
username: membership.public_user_data.username ?? null,
banned: membership.public_user_data.banned ?? false,
publicMetadata: membership.public_metadata ?? {},
createdAt: membership.created_at,
updatedAt: membership.updated_at,
}))
return {
success: true,
output: {
memberships,
totalCount: responseData.total_count ?? memberships.length,
success: true,
},
}
},
outputs: {
memberships: {
type: 'array',
description: 'Array of Clerk organization membership objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Membership ID' },
role: { type: 'string', description: 'Member role' },
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
permissions: {
type: 'array',
description: 'Permissions granted by the role',
items: { type: 'string' },
},
organizationId: { type: 'string', description: 'Organization ID' },
userId: { type: 'string', description: 'Member user ID' },
firstName: { type: 'string', description: 'Member first name', optional: true },
lastName: { type: 'string', description: 'Member last name', optional: true },
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
identifier: {
type: 'string',
description: 'Member identifier (e.g., email)',
optional: true,
},
username: { type: 'string', description: 'Member username', optional: true },
banned: { type: 'boolean', description: 'Whether the member is banned' },
publicMetadata: { type: 'json', description: 'Public metadata' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
},
},
},
totalCount: { type: 'number', description: 'Total number of memberships' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+157
View 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 (e.g., 10, 50, 100; range: 1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
},
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 (e.g., Acme Corp or acme-corp)',
},
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' },
},
}
+153
View File
@@ -0,0 +1,153 @@
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 (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC; 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 (e.g., 10, 50, 100; range: 1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
},
},
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' },
},
}
+253
View File
@@ -0,0 +1,253 @@
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 (e.g., 10, 50, 100; range: 1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
},
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 (e.g., user@example.com or user1@example.com,user2@example.com)',
},
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 (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC or comma-separated for multiple)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Search query to match across email, phone, username, and names (e.g., john or john@example.com)',
},
},
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' },
},
}
+89
View File
@@ -0,0 +1,89 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkLockUserParams,
ClerkLockUserResponse,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkLockUser')
export const clerkLockUserTool: ToolConfig<ClerkLockUserParams, ClerkLockUserResponse> = {
id: 'clerk_lock_user',
name: 'Lock User in Clerk',
description: 'Lock a user account, blocking sign-in attempts',
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 lock (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/lock`,
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: 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 lock 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,
banned: user.banned ?? false,
locked: user.locked ?? false,
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
updatedAt: user.updated_at,
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 },
banned: { type: 'boolean', description: 'Whether the user is banned' },
locked: { type: 'boolean', description: 'Whether the user is locked' },
lockoutExpiresInSeconds: {
type: 'number',
description: 'Seconds until lockout expires',
optional: true,
},
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,114 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkOrganizationMembership,
ClerkRemoveOrganizationMemberParams,
ClerkRemoveOrganizationMemberResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkRemoveOrganizationMember')
export const clerkRemoveOrganizationMemberTool: ToolConfig<
ClerkRemoveOrganizationMemberParams,
ClerkRemoveOrganizationMemberResponse
> = {
id: 'clerk_remove_organization_member',
name: 'Remove Organization Member from Clerk',
description: 'Remove a member from a Clerk organization',
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 of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the member to remove',
},
},
request: {
url: (params) =>
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships/${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: ClerkOrganizationMembership | 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 remove organization member from Clerk'
)
}
const membership = data as ClerkOrganizationMembership
return {
success: true,
output: {
id: membership.id,
role: membership.role,
roleName: membership.role_name ?? null,
permissions: membership.permissions ?? [],
organizationId: membership.organization.id,
userId: membership.public_user_data.user_id,
firstName: membership.public_user_data.first_name ?? null,
lastName: membership.public_user_data.last_name ?? null,
imageUrl: membership.public_user_data.image_url ?? null,
identifier: membership.public_user_data.identifier ?? null,
username: membership.public_user_data.username ?? null,
banned: membership.public_user_data.banned ?? false,
publicMetadata: membership.public_metadata ?? {},
createdAt: membership.created_at,
updatedAt: membership.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Membership ID' },
role: { type: 'string', description: 'Member role' },
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
permissions: {
type: 'array',
description: 'Permissions granted by the role',
items: { type: 'string' },
},
organizationId: { type: 'string', description: 'Organization ID' },
userId: { type: 'string', description: 'Member user ID' },
firstName: { type: 'string', description: 'Member first name', optional: true },
lastName: { type: 'string', description: 'Member last name', optional: true },
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
identifier: { type: 'string', description: 'Member identifier (e.g., email)', optional: true },
username: { type: 'string', description: 'Member username', optional: true },
banned: { type: 'boolean', description: 'Whether the member is banned' },
publicMetadata: { type: 'json', description: 'Public metadata' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
@@ -0,0 +1,89 @@
import { createLogger } from '@sim/logger'
import type {
ClerkActorToken,
ClerkApiError,
ClerkRevokeActorTokenParams,
ClerkRevokeActorTokenResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkRevokeActorToken')
export const clerkRevokeActorTokenTool: ToolConfig<
ClerkRevokeActorTokenParams,
ClerkRevokeActorTokenResponse
> = {
id: 'clerk_revoke_actor_token',
name: 'Revoke Actor Token in Clerk',
description: 'Revoke an actor token before it is used or expires',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
actorTokenId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the actor token to revoke',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/actor_tokens/${params.actorTokenId?.trim()}/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: ClerkActorToken | 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 actor token in Clerk'
)
}
const actorToken = data as ClerkActorToken
return {
success: true,
output: {
id: actorToken.id,
status: actorToken.status,
userId: actorToken.user_id,
actor: actorToken.actor ?? {},
token: actorToken.token ?? null,
url: actorToken.url ?? null,
createdAt: actorToken.created_at,
updatedAt: actorToken.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Actor token ID' },
status: { type: 'string', description: 'Actor token status (should be revoked)' },
userId: { type: 'string', description: 'ID of the impersonated user' },
actor: { type: 'json', description: 'Actor object identifying who is impersonating' },
token: { type: 'string', description: 'Signed actor token (JWT)', optional: true },
url: { type: 'string', description: 'Sign-in URL for the actor token', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+96
View 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 (e.g., sess_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId?.trim()}/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' },
},
}
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkUnbanUserParams,
ClerkUnbanUserResponse,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkUnbanUser')
export const clerkUnbanUserTool: ToolConfig<ClerkUnbanUserParams, ClerkUnbanUserResponse> = {
id: 'clerk_unban_user',
name: 'Unban User in Clerk',
description: 'Remove a ban from a user, allowing them to sign in again',
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 unban (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/unban`,
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: 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 unban 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,
banned: user.banned ?? false,
locked: user.locked ?? false,
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
updatedAt: user.updated_at,
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 },
banned: { type: 'boolean', description: 'Whether the user is banned' },
locked: { type: 'boolean', description: 'Whether the user is locked' },
lockoutExpiresInSeconds: {
type: 'number',
description: 'Seconds until lockout expires',
optional: true,
},
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+89
View File
@@ -0,0 +1,89 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkUnlockUserParams,
ClerkUnlockUserResponse,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkUnlockUser')
export const clerkUnlockUserTool: ToolConfig<ClerkUnlockUserParams, ClerkUnlockUserResponse> = {
id: 'clerk_unlock_user',
name: 'Unlock User in Clerk',
description: 'Unlock a previously locked user account',
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 unlock (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/unlock`,
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: 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 unlock 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,
banned: user.banned ?? false,
locked: user.locked ?? false,
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
updatedAt: user.updated_at,
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 },
banned: { type: 'boolean', description: 'Whether the user is banned' },
locked: { type: 'boolean', description: 'Whether the user is locked' },
lockoutExpiresInSeconds: {
type: 'number',
description: 'Seconds until lockout expires',
optional: true,
},
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+138
View File
@@ -0,0 +1,138 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkOrganization,
ClerkUpdateOrganizationParams,
ClerkUpdateOrganizationResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkUpdateOrganization')
export const clerkUpdateOrganizationTool: ToolConfig<
ClerkUpdateOrganizationParams,
ClerkUpdateOrganizationResponse
> = {
id: 'clerk_update_organization',
name: 'Update Organization in Clerk',
description: 'Update an existing 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',
},
organizationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the organization to update (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Name of the organization',
},
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)',
},
adminDeleteEnabled: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether admins can delete the organization',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId?.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.name !== undefined) body.name = params.name
if (params.slug !== undefined) body.slug = params.slug
if (params.maxAllowedMemberships !== undefined)
body.max_allowed_memberships = params.maxAllowedMemberships
if (params.adminDeleteEnabled !== undefined)
body.admin_delete_enabled = params.adminDeleteEnabled
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 update 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: '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' },
},
}
@@ -0,0 +1,123 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkOrganizationMembership,
ClerkUpdateOrganizationMembershipParams,
ClerkUpdateOrganizationMembershipResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkUpdateOrganizationMembership')
export const clerkUpdateOrganizationMembershipTool: ToolConfig<
ClerkUpdateOrganizationMembershipParams,
ClerkUpdateOrganizationMembershipResponse
> = {
id: 'clerk_update_organization_membership',
name: 'Update Organization Membership in Clerk',
description: "Change a member's role within a Clerk organization",
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 of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the member whose role is being changed',
},
role: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'New role to assign, e.g. org:admin or org:member',
},
},
request: {
url: (params) =>
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships/${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) => ({
role: params.role,
}),
},
transformResponse: async (response: Response) => {
const data: ClerkOrganizationMembership | 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 organization membership in Clerk'
)
}
const membership = data as ClerkOrganizationMembership
return {
success: true,
output: {
id: membership.id,
role: membership.role,
roleName: membership.role_name ?? null,
permissions: membership.permissions ?? [],
organizationId: membership.organization.id,
userId: membership.public_user_data.user_id,
firstName: membership.public_user_data.first_name ?? null,
lastName: membership.public_user_data.last_name ?? null,
imageUrl: membership.public_user_data.image_url ?? null,
identifier: membership.public_user_data.identifier ?? null,
username: membership.public_user_data.username ?? null,
banned: membership.public_user_data.banned ?? false,
publicMetadata: membership.public_metadata ?? {},
createdAt: membership.created_at,
updatedAt: membership.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Membership ID' },
role: { type: 'string', description: 'Member role' },
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
permissions: {
type: 'array',
description: 'Permissions granted by the role',
items: { type: 'string' },
},
organizationId: { type: 'string', description: 'Organization ID' },
userId: { type: 'string', description: 'Member user ID' },
firstName: { type: 'string', description: 'Member first name', optional: true },
lastName: { type: 'string', description: 'Member last name', optional: true },
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
identifier: { type: 'string', description: 'Member identifier (e.g., email)', optional: true },
username: { type: 'string', description: 'Member username', optional: true },
banned: { type: 'boolean', description: 'Whether the member is banned' },
publicMetadata: { type: 'json', description: 'Public metadata' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}
+225
View 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 (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
},
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' },
},
}